diff options
Diffstat (limited to 'src/zenserver/config.cpp')
| -rw-r--r-- | src/zenserver/config.cpp | 1091 |
1 files changed, 791 insertions, 300 deletions
diff --git a/src/zenserver/config.cpp b/src/zenserver/config.cpp index 0e7aac079..2d286b02a 100644 --- a/src/zenserver/config.cpp +++ b/src/zenserver/config.cpp @@ -135,7 +135,795 @@ ParseBucketConfigs(std::span<std::string> Buckets) return Cfg; } -void ParseConfigFile(const std::filesystem::path& Path, ZenServerOptions& ServerOptions); +static std::string +MakeSafePath(const std::string& Path) +{ +#if ZEN_PLATFORM_WINDOWS + if (Path.empty()) + { + return Path; + } + + std::string FixedPath = Path; + std::replace(FixedPath.begin(), FixedPath.end(), '/', '\\'); + if (!FixedPath.starts_with("\\\\?\\")) + { + FixedPath.insert(0, "\\\\?\\"); + } + return FixedPath; +#else + return Path; +#endif +}; + +namespace LuaConfig { + +void +EscapeBackslash(std::string& InOutString) +{ + std::size_t BackslashPos = InOutString.find('\\'); + if (BackslashPos != std::string::npos) + { + std::size_t Offset = 0; + zen::ExtendableStringBuilder<512> PathBuilder; + while (BackslashPos != std::string::npos) + { + PathBuilder.Append(InOutString.substr(Offset, BackslashPos + 1 - Offset)); + PathBuilder.Append('\\'); + Offset = BackslashPos + 1; + BackslashPos = InOutString.find('\\', Offset); + } + PathBuilder.Append(InOutString.substr(Offset, BackslashPos)); + InOutString = PathBuilder.ToString(); + } +} + +class OptionValue +{ +public: + virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) = 0; + virtual void Parse(sol::object Object) = 0; + + virtual ~OptionValue() {} +}; + +typedef std::shared_ptr<OptionValue> TOptionValue; + +class StringOption : public OptionValue +{ +public: + StringOption(std::string& Value) : Value(Value) {} + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override + { + StringBuilder.Append(fmt::format("\"{}\"", Value)); + } + virtual void Parse(sol::object Object) override { Value = Object.as<std::string>(); } + std::string& Value; +}; + +class FilePathOption : public OptionValue +{ +public: + FilePathOption(std::filesystem::path& Value) : Value(Value) {} + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override + { + std::string Path = Value.string(); + EscapeBackslash(Path); + StringBuilder.Append(fmt::format("\"{}\"", Path)); + } + virtual void Parse(sol::object Object) override + { + std::string Str = Object.as<std::string>(); + if (!Str.empty()) + { + Value = MakeSafePath(Str); + } + } + std::filesystem::path& Value; +}; + +class BoolOption : public OptionValue +{ +public: + BoolOption(bool& Value) : Value(Value) {} + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override { StringBuilder.Append(Value ? "true" : "false"); } + virtual void Parse(sol::object Object) override { Value = Object.as<bool>(); } + bool& Value; +}; + +class CachePolicyOption : public OptionValue +{ +public: + CachePolicyOption(UpstreamCachePolicy& Value) : Value(Value) {} + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override + { + switch (Value) + { + case UpstreamCachePolicy::Read: + StringBuilder.Append("readonly"); + break; + case UpstreamCachePolicy::Write: + StringBuilder.Append("writeonly"); + break; + case UpstreamCachePolicy::Disabled: + StringBuilder.Append("disabled"); + break; + case UpstreamCachePolicy::ReadWrite: + StringBuilder.Append("readwrite"); + break; + default: + ZEN_ASSERT(false); + } + } + virtual void Parse(sol::object Object) override + { + std::string PolicyString = Object.as<std::string>(); + if (PolicyString == "readonly") + { + Value = UpstreamCachePolicy::Read; + } + else if (PolicyString == "writeonly") + { + Value = UpstreamCachePolicy::Write; + } + else if (PolicyString == "disabled") + { + Value = UpstreamCachePolicy::Disabled; + } + else if (PolicyString == "readwrite") + { + Value = UpstreamCachePolicy::ReadWrite; + } + } + UpstreamCachePolicy& Value; +}; + +template<Integral T> +class NumberOption : public OptionValue +{ +public: + NumberOption(T& Value) : Value(Value) {} + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override { StringBuilder.Append(fmt::format("{}", Value)); } + virtual void Parse(sol::object Object) override { Value = Object.as<T>(); } + T& Value; +}; + +class LuaContainerWriter +{ +public: + LuaContainerWriter(zen::StringBuilderBase& StringBuilder, std::string_view Indent) + : StringBuilder(StringBuilder) + , InitialIndent(Indent.length()) + , LocalIndent(Indent) + { + StringBuilder.Append("{\n"); + LocalIndent.push_back('\t'); + } + ~LuaContainerWriter() + { + LocalIndent.pop_back(); + StringBuilder.Append(LocalIndent); + StringBuilder.Append("}"); + } + + void BeginContainer(std::string_view Name) + { + StringBuilder.Append(LocalIndent); + if (!Name.empty()) + { + StringBuilder.Append(Name); + StringBuilder.Append(" = {\n"); + } + else + { + StringBuilder.Append("{\n"); + } + LocalIndent.push_back('\t'); + } + void WriteValue(std::string_view Name, std::string_view Value) + { + if (Name.empty()) + { + StringBuilder.Append(fmt::format("{}\"{}\",\n", LocalIndent, Value)); + } + else + { + StringBuilder.Append(fmt::format("{}{} = \"{}\",\n", LocalIndent, Name, Value)); + } + } + void EndContainer() + { + LocalIndent.pop_back(); + StringBuilder.Append(LocalIndent); + StringBuilder.Append("}"); + StringBuilder.Append(",\n"); + } + +private: + zen::StringBuilderBase& StringBuilder; + const std::size_t InitialIndent; + std::string LocalIndent; +}; + +class StringArrayOption : public OptionValue +{ +public: + StringArrayOption(std::vector<std::string>& Value) : Value(Value) {} + virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override + { + if (Value.empty()) + { + StringBuilder.Append("{}"); + } + if (Value.size() == 1) + { + StringBuilder.Append(fmt::format("\"{}\"", Value[0])); + } + else + { + LuaContainerWriter Writer(StringBuilder, Indent); + for (std::string String : Value) + { + Writer.WriteValue("", String); + } + } + } + virtual void Parse(sol::object Object) override + { + if (Object.get_type() == sol::type::string) + { + Value.push_back(Object.as<std::string>()); + } + else if (Object.get_type() == sol::type::table) + { + for (const auto& Kv : Object.as<sol::table>()) + { + Value.push_back(Kv.second.as<std::string>()); + } + } + } + +private: + std::vector<std::string>& Value; +}; + +class ZenAuthConfigOption : public OptionValue +{ +public: + ZenAuthConfigOption(ZenAuthConfig& Value) : Value(Value) {} + virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override + { + if (Value.OpenIdProviders.empty()) + { + StringBuilder.Append("{}"); + return; + } + LuaContainerWriter Writer(StringBuilder, Indent); + for (const ZenOpenIdProviderConfig& Config : Value.OpenIdProviders) + { + Writer.BeginContainer(""); + { + Writer.WriteValue("name", Config.Name); + Writer.WriteValue("url", Config.Url); + Writer.WriteValue("clientid", Config.ClientId); + } + Writer.EndContainer(); + } + } + virtual void Parse(sol::object Object) override + { + if (sol::optional<sol::table> OpenIdProviders = Object.as<sol::table>()) + { + for (const auto& Kv : OpenIdProviders.value()) + { + if (sol::optional<sol::table> OpenIdProvider = Kv.second.as<sol::table>()) + { + std::string Name = OpenIdProvider.value().get_or("name", std::string("Default")); + std::string Url = OpenIdProvider.value().get_or("url", std::string()); + std::string ClientId = OpenIdProvider.value().get_or("clientid", std::string()); + + Value.OpenIdProviders.push_back({.Name = std::move(Name), .Url = std::move(Url), .ClientId = std::move(ClientId)}); + } + } + } + } + ZenAuthConfig& Value; +}; + +class ZenObjectStoreConfigOption : public OptionValue +{ +public: + ZenObjectStoreConfigOption(ZenObjectStoreConfig& Value) : Value(Value) {} + virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override + { + if (Value.Buckets.empty()) + { + StringBuilder.Append("{}"); + return; + } + LuaContainerWriter Writer(StringBuilder, Indent); + for (const ZenObjectStoreConfig::BucketConfig& Config : Value.Buckets) + { + Writer.BeginContainer(""); + { + Writer.WriteValue("name", Config.Name); + std::string Directory = Config.Directory.string(); + EscapeBackslash(Directory); + Writer.WriteValue("directory", Directory); + } + Writer.EndContainer(); + } + } + virtual void Parse(sol::object Object) override + { + if (sol::optional<sol::table> Buckets = Object.as<sol::table>()) + { + for (const auto& Kv : Buckets.value()) + { + if (sol::optional<sol::table> Bucket = Kv.second.as<sol::table>()) + { + std::string Name = Bucket.value().get_or("name", std::string("Default")); + std::string Directory = Bucket.value().get_or("directory", std::string()); + + Value.Buckets.push_back({.Name = std::move(Name), .Directory = MakeSafePath(Directory)}); + } + } + } + } + ZenObjectStoreConfig& Value; +}; + +std::shared_ptr<OptionValue> +MakeOption(std::string& Value) +{ + return std::make_shared<StringOption>(Value); +}; + +std::shared_ptr<OptionValue> +MakeOption(std::filesystem::path& Value) +{ + return std::make_shared<FilePathOption>(Value); +}; + +template<Integral T> +std::shared_ptr<OptionValue> +MakeOption(T& Value) +{ + return std::make_shared<NumberOption<T>>(Value); +}; + +std::shared_ptr<OptionValue> +MakeOption(bool& Value) +{ + return std::make_shared<BoolOption>(Value); +}; + +std::shared_ptr<OptionValue> +MakeOption(UpstreamCachePolicy& Value) +{ + return std::make_shared<CachePolicyOption>(Value); +}; + +std::shared_ptr<OptionValue> +MakeOption(std::vector<std::string>& Value) +{ + return std::make_shared<StringArrayOption>(Value); +}; + +std::shared_ptr<OptionValue> +MakeOption(ZenAuthConfig& Value) +{ + return std::make_shared<ZenAuthConfigOption>(Value); +}; + +std::shared_ptr<OptionValue> +MakeOption(ZenObjectStoreConfig& Value) +{ + return std::make_shared<ZenObjectStoreConfigOption>(Value); +}; + +struct Option +{ + std::string CommandLineOptionName; + TOptionValue Value; +}; + +struct Options +{ +public: + template<typename T> + void AddOption(std::string_view Key, T& Value, std::string_view CommandLineOptionName = "") + { + OptionMap.insert_or_assign(std::string(Key), + Option{.CommandLineOptionName = std::string(CommandLineOptionName), .Value = MakeOption(Value)}); + }; + + void Parse(const std::filesystem::path& Path, const cxxopts::ParseResult& CmdLineResult) + { + zen::IoBuffer LuaScript = zen::IoBufferBuilder::MakeFromFile(Path); + + if (LuaScript) + { + sol::state lua; + + lua.open_libraries(sol::lib::base); + + lua.set_function("getenv", [&](const std::string env) -> sol::object { +#if ZEN_PLATFORM_WINDOWS + 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())); +#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC + char* EnvVariable = getenv(env.c_str()); + if (EnvVariable == nullptr) + { + return sol::make_object(lua, sol::lua_nil); + } + return sol::make_object(lua, EnvVariable); +#else + ZEN_UNUSED(env); + return sol::make_object(lua, sol::lua_nil); +#endif + }); + + 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(fmt::format("{} error: {}", ErrorString, err.what())); + } + + config(); + } + catch (std::exception& e) + { + throw std::runtime_error(fmt::format("failed to load config script ('{}'): {}", Path, e.what()).c_str()); + } + + Parse(lua, CmdLineResult); + } + } + + void Parse(const sol::state& LuaState, const cxxopts::ParseResult& CmdLineResult) + { + for (auto It : LuaState) + { + sol::object Key = It.first; + sol::type KeyType = Key.get_type(); + if (KeyType == sol::type::string) + { + sol::type ValueType = It.second.get_type(); + switch (ValueType) + { + case sol::type::table: + { + std::string Name = Key.as<std::string>(); + if (Name.starts_with("_")) + { + continue; + } + if (Name == "base") + { + continue; + } + Traverse(It.second.as<sol::table>(), Name, CmdLineResult); + } + break; + default: + break; + } + } + } + } + + void Touch(std::string_view Key) { UsedKeys.insert(std::string(Key)); } + + void Print(zen::StringBuilderBase& SB, const cxxopts::ParseResult& CmdLineResult) + { + for (auto It : OptionMap) + { + if (CmdLineResult.count(It.second.CommandLineOptionName) != 0) + { + UsedKeys.insert(It.first); + } + } + + std::vector<std::string> SortedKeys(UsedKeys.begin(), UsedKeys.end()); + std::sort(SortedKeys.begin(), SortedKeys.end()); + auto GetTablePath = [](const std::string& Key) -> std::vector<std::string> { + std::vector<std::string> Path; + zen::ForEachStrTok(Key, '.', [&Path](std::string_view Part) { + Path.push_back(std::string(Part)); + return true; + }); + return Path; + }; + std::vector<std::string> CurrentTablePath; + std::string Indent; + auto It = SortedKeys.begin(); + for (const std::string& Key : SortedKeys) + { + std::vector<std::string> KeyPath = GetTablePath(Key); + std::string Name = KeyPath.back(); + KeyPath.pop_back(); + if (CurrentTablePath != KeyPath) + { + size_t EqualCount = 0; + while (EqualCount < CurrentTablePath.size() && EqualCount < KeyPath.size() && + CurrentTablePath[EqualCount] == KeyPath[EqualCount]) + { + EqualCount++; + } + while (CurrentTablePath.size() > EqualCount) + { + CurrentTablePath.pop_back(); + Indent.pop_back(); + SB.Append(Indent); + SB.Append("}"); + if (CurrentTablePath.size() == EqualCount && !Indent.empty() && KeyPath.size() >= EqualCount) + { + SB.Append(","); + } + SB.Append("\n"); + if (Indent.empty()) + { + SB.Append("\n"); + } + } + while (EqualCount < KeyPath.size()) + { + SB.Append(Indent); + SB.Append(KeyPath[EqualCount]); + SB.Append(" = {\n"); + Indent.push_back('\t'); + CurrentTablePath.push_back(KeyPath[EqualCount]); + EqualCount++; + } + } + + SB.Append(Indent); + SB.Append(Name); + SB.Append(" = "); + OptionMap[Key].Value->Print(Indent, SB); + SB.Append(",\n"); + } + while (!CurrentTablePath.empty()) + { + Indent.pop_back(); + SB.Append(Indent); + SB.Append("}\n"); + CurrentTablePath.pop_back(); + } + } + +private: + void Traverse(sol::table Table, std::string_view PathPrefix, const cxxopts::ParseResult& CmdLineResult) + { + for (auto It : Table) + { + sol::object Key = It.first; + sol::type KeyType = Key.get_type(); + if (KeyType == sol::type::string || KeyType == sol::type::number) + { + sol::type ValueType = It.second.get_type(); + switch (ValueType) + { + case sol::type::table: + case sol::type::string: + case sol::type::number: + case sol::type::boolean: + { + std::string Name = Key.as<std::string>(); + if (Name.starts_with("_")) + { + continue; + } + Name = std::string(PathPrefix) + "." + Key.as<std::string>(); + auto OptionIt = OptionMap.find(Name); + if (OptionIt != OptionMap.end()) + { + UsedKeys.insert(Name); + if (CmdLineResult.count(OptionIt->second.CommandLineOptionName) != 0) + { + continue; + } + OptionIt->second.Value->Parse(It.second); + continue; + } + if (ValueType == sol::type::table) + { + if (Name == "base") + { + continue; + } + Traverse(It.second.as<sol::table>(), Name, CmdLineResult); + } + } + break; + default: + break; + } + } + } + } + + std::unordered_map<std::string, Option> OptionMap; + std::unordered_set<std::string> UsedKeys; +}; + +} // namespace LuaConfig + +void +ParseConfigFile(const std::filesystem::path& Path, ZenServerOptions& ServerOptions, const cxxopts::ParseResult& CmdLineResult) +{ + using namespace std::literals; + + LuaConfig::Options LuaOptions; + + ////// server + LuaOptions.AddOption("server.dedicated"sv, ServerOptions.IsDedicated, "dedicated"sv); + LuaOptions.AddOption("server.logid"sv, ServerOptions.LogId, "log-id"sv); + LuaOptions.AddOption("server.sentry.disable"sv, ServerOptions.NoSentry, "no-sentry"sv); + LuaOptions.AddOption("server.sentry.allowpersonalinfo"sv, ServerOptions.SentryAllowPII, "sentry-allow-personal-info"sv); + LuaOptions.AddOption("server.datadir"sv, ServerOptions.DataDir, "data-dir"sv); + LuaOptions.AddOption("server.contentdir"sv, ServerOptions.ContentDir, "content-dir"sv); + LuaOptions.AddOption("server.abslog"sv, ServerOptions.AbsLogFile, "abslog"sv); + LuaOptions.AddOption("server.debug"sv, ServerOptions.IsDebug, "debug"sv); + + ////// objectstore + LuaOptions.AddOption("server.objectstore.enabled"sv, ServerOptions.ObjectStoreEnabled, "objectstore-enabled"sv); + LuaOptions.AddOption("server.objectstore.buckets"sv, ServerOptions.ObjectStoreConfig); + + ////// network + LuaOptions.AddOption("network.httpserverclass"sv, ServerOptions.HttpServerClass, "http"sv); + LuaOptions.AddOption("network.port"sv, ServerOptions.BasePort, "port"sv); + LuaOptions.AddOption("network.websocket.port"sv, ServerOptions.WebSocketPort, "websocket-port"sv); + LuaOptions.AddOption("network.websocket.threadcount"sv, ServerOptions.WebSocketThreads, "websocket-threads"sv); + + ////// trace + LuaOptions.AddOption("trace.host"sv, ServerOptions.TraceHost, "tracehost"sv); + LuaOptions.AddOption("trace.file"sv, ServerOptions.TraceFile, "tracefile"sv); + + ////// cache + LuaOptions.AddOption("cache.enable"sv, ServerOptions.StructuredCacheEnabled); + + ////// cache.upstream + LuaOptions.AddOption("cache.upstream.policy"sv, ServerOptions.UpstreamCacheConfig.CachePolicy, "upstream-cache-policy"sv); + LuaOptions.AddOption("cache.upstream.upstreamthreadcount"sv, + ServerOptions.UpstreamCacheConfig.UpstreamThreadCount, + "upstream-thread-count"sv); + LuaOptions.AddOption("cache.upstream.connecttimeoutms"sv, + ServerOptions.UpstreamCacheConfig.ConnectTimeoutMilliseconds, + "upstream-connect-timeout-ms"sv); + LuaOptions.AddOption("cache.upstream.timeoutms"sv, ServerOptions.UpstreamCacheConfig.TimeoutMilliseconds, "upstream-timeout-ms"sv); + + ////// cache.upstream.jupiter + LuaOptions.AddOption("cache.upstream.jupiter.name"sv, ServerOptions.UpstreamCacheConfig.JupiterConfig.Name); + LuaOptions.AddOption("cache.upstream.jupiter.url"sv, ServerOptions.UpstreamCacheConfig.JupiterConfig.Url, "upstream-jupiter-url"sv); + LuaOptions.AddOption("cache.upstream.jupiter.oauthprovider"sv, + ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthUrl, + "upstream-jupiter-oauth-url"sv); + LuaOptions.AddOption("cache.upstream.jupiter.oauthclientid"sv, + ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthClientId, + "upstream-jupiter-oauth-clientid"); + LuaOptions.AddOption("cache.upstream.jupiter.oauthclientsecret"sv, + ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthClientSecret, + "upstream-jupiter-oauth-clientsecret"sv); + LuaOptions.AddOption("cache.upstream.jupiter.openidprovider"sv, + ServerOptions.UpstreamCacheConfig.JupiterConfig.OpenIdProvider, + "upstream-jupiter-openid-provider"sv); + LuaOptions.AddOption("cache.upstream.jupiter.token"sv, + ServerOptions.UpstreamCacheConfig.JupiterConfig.AccessToken, + "upstream-jupiter-token"sv); + LuaOptions.AddOption("cache.upstream.jupiter.namespace"sv, + ServerOptions.UpstreamCacheConfig.JupiterConfig.Namespace, + "upstream-jupiter-namespace"sv); + LuaOptions.AddOption("cache.upstream.jupiter.ddcnamespace"sv, + ServerOptions.UpstreamCacheConfig.JupiterConfig.DdcNamespace, + "upstream-jupiter-namespace-ddc"sv); + + ////// cache.upstream.zen + // LuaOptions.AddOption("cache.upstream.zen"sv, ServerOptions.UpstreamCacheConfig.ZenConfig); + LuaOptions.AddOption("cache.upstream.zen.name"sv, ServerOptions.UpstreamCacheConfig.ZenConfig.Name); + LuaOptions.AddOption("cache.upstream.zen.dns"sv, ServerOptions.UpstreamCacheConfig.ZenConfig.Dns); + LuaOptions.AddOption("cache.upstream.zen.url"sv, ServerOptions.UpstreamCacheConfig.ZenConfig.Urls); + + ////// exec + LuaOptions.AddOption("exec.enable"sv, ServerOptions.ExecServiceEnabled); + + ////// compute + LuaOptions.AddOption("compute.enable"sv, ServerOptions.ComputeServiceEnabled); + + LuaOptions.AddOption("compute.upstream.horde.name"sv, ServerOptions.UpstreamCacheConfig.HordeConfig.Name); + LuaOptions.AddOption("compute.upstream.horde.url"sv, ServerOptions.UpstreamCacheConfig.HordeConfig.Url, "upstream-horde-url"sv); + LuaOptions.AddOption("compute.upstream.horde.oauthprovider"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.OAuthUrl, + "upstream-horde-oauth-url"sv); + LuaOptions.AddOption("compute.upstream.horde.oauthclientid"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.OAuthClientId, + "upstream-horde-oauth-clientid"sv); + LuaOptions.AddOption("compute.upstream.horde.oauthclientsecret"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.OAuthClientSecret, + "upstream-horde-oauth-clientsecret"sv); + LuaOptions.AddOption("compute.upstream.horde.openidprovider"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.OpenIdProvider, + "upstream-horde-openid-provider"sv); + LuaOptions.AddOption("compute.upstream.horde.token"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.AccessToken, + "upstream-horde-token"sv); + LuaOptions.AddOption("compute.upstream.horde.cluster"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.Cluster, + "upstream-horde-cluster"sv); + LuaOptions.AddOption("compute.upstream.horde.namespace"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.Namespace, + "upstream-horde-namespace"sv); + + ////// compute storage + LuaOptions.AddOption("compute.upstream.storage.name"sv, ServerOptions.UpstreamCacheConfig.HordeConfig.Name); + LuaOptions.AddOption("compute.upstream.storage.url"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.Url, + "upstream-horde-storage-oauth-url"sv); + LuaOptions.AddOption("compute.upstream.storage.oauthprovider"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.StorageOAuthUrl, + "upstream-horde-storage-oauth-url"sv); + LuaOptions.AddOption("compute.upstream.storage.oauthclientid"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.StorageOAuthClientId, + "upstream-horde-storage-oauth-clientid"sv); + LuaOptions.AddOption("compute.upstream.storage.oauthclientsecret"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.StorageOAuthClientSecret, + "upstream-horde-storage-oauth-clientsecret"sv); + LuaOptions.AddOption("compute.upstream.storage.openidprovider"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.StorageOpenIdProvider, + "upstream-horde-storage-openid-provider"sv); + LuaOptions.AddOption("compute.upstream.storage.token"sv, + ServerOptions.UpstreamCacheConfig.HordeConfig.StorageAccessToken, + "upstream-horde-storage-token"sv); + + LuaOptions.AddOption("gc.enabled"sv, ServerOptions.GcConfig.Enabled, "gc-enabled"sv); + + LuaOptions.AddOption("gc.monitorintervalseconds"sv, ServerOptions.GcConfig.MonitorIntervalSeconds, "gc-monitor-interval-seconds"sv); + LuaOptions.AddOption("gc.intervalseconds"sv, ServerOptions.GcConfig.IntervalSeconds, "gc-interval-seconds"sv); + LuaOptions.AddOption("gc.collectsmallobjects"sv, ServerOptions.GcConfig.CollectSmallObjects, "gc-small-objects"sv); + LuaOptions.AddOption("gc.diskreservesize"sv, ServerOptions.GcConfig.DiskReserveSize, "disk-reserve-size"sv); + LuaOptions.AddOption("gc.disksizesoftlimit"sv, ServerOptions.GcConfig.DiskSizeSoftLimit, "gc-disksize-softlimit"sv); + LuaOptions.AddOption("gc.lowdiskspacethreshold"sv, + ServerOptions.GcConfig.MinimumFreeDiskSpaceToAllowWrites, + "gc-low-diskspace-threshold"sv); + + ////// gc + LuaOptions.AddOption("gc.cache.maxdurationseconds"sv, ServerOptions.GcConfig.Cache.MaxDurationSeconds, "gc-cache-duration-seconds"sv); + + ////// security + LuaOptions.AddOption("security.encryptionaeskey"sv, ServerOptions.EncryptionKey, "encryption-aes-key"sv); + LuaOptions.AddOption("security.encryptionaesiv"sv, ServerOptions.EncryptionIV, "encryption-aes-iv"sv); + LuaOptions.AddOption("security.openidproviders"sv, ServerOptions.AuthConfig); + + LuaOptions.Parse(Path, CmdLineResult); + + // These have special command line processing so we make sure we export them if they were configured on command line + if (!ServerOptions.AuthConfig.OpenIdProviders.empty()) + { + LuaOptions.Touch("security.openidproviders"sv); + } + if (!ServerOptions.ObjectStoreConfig.Buckets.empty()) + { + LuaOptions.Touch("server.objectstore.buckets"sv); + } +} void ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions) @@ -571,25 +1359,6 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions) exit(0); } - auto MakeSafePath = [](const std::string& Path) { -#if ZEN_PLATFORM_WINDOWS - if (Path.empty()) - { - return Path; - } - - std::string FixedPath = Path; - std::replace(FixedPath.begin(), FixedPath.end(), '/', '\\'); - if (!FixedPath.starts_with("\\\\?\\")) - { - FixedPath.insert(0, "\\\\?\\"); - } - return FixedPath; -#else - return Path; -#endif - }; - ServerOptions.DataDir = MakeSafePath(DataDir); ServerOptions.ContentDir = MakeSafePath(ContentDir); ServerOptions.AbsLogFile = MakeSafePath(AbsLogFile); @@ -611,11 +1380,11 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions) if (!ServerOptions.ConfigFile.empty()) { - ParseConfigFile(ServerOptions.ConfigFile, ServerOptions); + ParseConfigFile(ServerOptions.ConfigFile, ServerOptions, result); } else { - ParseConfigFile(ServerOptions.DataDir / "zen_cfg.lua", ServerOptions); + ParseConfigFile(ServerOptions.DataDir / "zen_cfg.lua", ServerOptions, result); } ValidateOptions(ServerOptions); @@ -643,281 +1412,3 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions) ServerOptions.AbsLogFile = ServerOptions.DataDir / "logs" / "zenserver.log"; } } - -void -ParseConfigFile(const std::filesystem::path& Path, ZenServerOptions& ServerOptions) -{ - zen::IoBuffer LuaScript = zen::IoBufferBuilder::MakeFromFile(Path); - - if (LuaScript) - { - sol::state lua; - - lua.open_libraries(sol::lib::base); - - lua.set_function("getenv", [&](const std::string env) -> sol::object { -#if ZEN_PLATFORM_WINDOWS - 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())); -#else - ZEN_UNUSED(env); - return sol::make_object(lua, sol::lua_nil); -#endif - }); - - 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(fmt::format("{} error: {}", ErrorString, err.what())); - } - - config(); - } - catch (std::exception& e) - { - throw std::runtime_error(fmt::format("failed to load config script ('{}'): {}", Path, e.what()).c_str()); - } - - if (sol::optional<sol::table> ServerConfig = lua["server"]) - { - if (ServerOptions.DataDir.empty()) - { - if (sol::optional<std::string> Opt = ServerConfig.value()["datadir"]) - { - ServerOptions.DataDir = Opt.value(); - } - } - - if (ServerOptions.ContentDir.empty()) - { - if (sol::optional<std::string> Opt = ServerConfig.value()["contentdir"]) - { - ServerOptions.ContentDir = Opt.value(); - } - } - - if (ServerOptions.AbsLogFile.empty()) - { - if (sol::optional<std::string> Opt = ServerConfig.value()["abslog"]) - { - ServerOptions.AbsLogFile = Opt.value(); - } - } - - ServerOptions.IsDebug = ServerConfig->get_or("debug", ServerOptions.IsDebug); - } - - if (sol::optional<sol::table> NetworkConfig = lua["network"]) - { - if (sol::optional<std::string> Opt = NetworkConfig.value()["httpserverclass"]) - { - ServerOptions.HttpServerClass = Opt.value(); - } - - ServerOptions.BasePort = NetworkConfig->get_or<int>("port", ServerOptions.BasePort); - } - - 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<sol::optional<std::string>>(Key); MaybeValue.has_value() && OutValue.empty()) - { - OutValue = MaybeValue.value(); - } - }; - - if (sol::optional<sol::table> StructuredCacheConfig = lua["cache"]) - { - ServerOptions.StructuredCacheEnabled = StructuredCacheConfig->get_or("enable", ServerOptions.StructuredCacheEnabled); - - if (auto UpstreamConfig = StructuredCacheConfig->get<sol::optional<sol::table>>("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<sol::optional<sol::table>>("jupiter")) - { - UpdateStringValueFromConfig(JupiterConfig.value(), - std::string_view("name"), - ServerOptions.UpstreamCacheConfig.JupiterConfig.Name); - UpdateStringValueFromConfig(JupiterConfig.value(), - std::string_view("url"), - ServerOptions.UpstreamCacheConfig.JupiterConfig.Url); - UpdateStringValueFromConfig(JupiterConfig.value(), - std::string_view("oauthprovider"), - ServerOptions.UpstreamCacheConfig.JupiterConfig.OAuthUrl); - 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("openidprovider"), - ServerOptions.UpstreamCacheConfig.JupiterConfig.OpenIdProvider); - UpdateStringValueFromConfig(JupiterConfig.value(), - std::string_view("token"), - ServerOptions.UpstreamCacheConfig.JupiterConfig.AccessToken); - UpdateStringValueFromConfig(JupiterConfig.value(), - std::string_view("namespace"), - ServerOptions.UpstreamCacheConfig.JupiterConfig.Namespace); - UpdateStringValueFromConfig(JupiterConfig.value(), - std::string_view("ddcnamespace"), - ServerOptions.UpstreamCacheConfig.JupiterConfig.DdcNamespace); - }; - - if (auto ZenConfig = UpstreamConfig->get<sol::optional<sol::table>>("zen")) - { - ServerOptions.UpstreamCacheConfig.ZenConfig.Name = ZenConfig.value().get_or("name", std::string("Zen")); - - if (auto Url = ZenConfig.value().get<sol::optional<std::string>>("url")) - { - ServerOptions.UpstreamCacheConfig.ZenConfig.Urls.push_back(Url.value()); - } - else if (auto Urls = ZenConfig.value().get<sol::optional<sol::table>>("url")) - { - for (const auto& Kv : Urls.value()) - { - ServerOptions.UpstreamCacheConfig.ZenConfig.Urls.push_back(Kv.second.as<std::string>()); - } - } - - if (auto Dns = ZenConfig.value().get<sol::optional<std::string>>("dns")) - { - ServerOptions.UpstreamCacheConfig.ZenConfig.Dns.push_back(Dns.value()); - } - else if (auto DnsArray = ZenConfig.value().get<sol::optional<sol::table>>("dns")) - { - for (const auto& Kv : DnsArray.value()) - { - ServerOptions.UpstreamCacheConfig.ZenConfig.Dns.push_back(Kv.second.as<std::string>()); - } - } - } - } - } - - if (sol::optional<sol::table> ExecConfig = lua["exec"]) - { - ServerOptions.ExecServiceEnabled = ExecConfig->get_or("enable", ServerOptions.ExecServiceEnabled); - } - - if (sol::optional<sol::table> ComputeConfig = lua["compute"]) - { - ServerOptions.ComputeServiceEnabled = ComputeConfig->get_or("enable", ServerOptions.ComputeServiceEnabled); - - if (auto UpstreamConfig = ComputeConfig->get<sol::optional<sol::table>>("upstream")) - { - if (auto HordeConfig = UpstreamConfig->get<sol::optional<sol::table>>("horde")) - { - UpdateStringValueFromConfig(HordeConfig.value(), - std::string_view("name"), - ServerOptions.UpstreamCacheConfig.HordeConfig.Name); - UpdateStringValueFromConfig(HordeConfig.value(), - std::string_view("url"), - ServerOptions.UpstreamCacheConfig.HordeConfig.Url); - UpdateStringValueFromConfig(HordeConfig.value(), - std::string_view("oauthprovider"), - ServerOptions.UpstreamCacheConfig.HordeConfig.OAuthUrl); - UpdateStringValueFromConfig(HordeConfig.value(), - std::string_view("oauthclientid"), - ServerOptions.UpstreamCacheConfig.HordeConfig.OAuthClientId); - UpdateStringValueFromConfig(HordeConfig.value(), - std::string_view("oauthclientsecret"), - ServerOptions.UpstreamCacheConfig.HordeConfig.OAuthClientSecret); - UpdateStringValueFromConfig(HordeConfig.value(), - std::string_view("openidprovider"), - ServerOptions.UpstreamCacheConfig.HordeConfig.OpenIdProvider); - UpdateStringValueFromConfig(HordeConfig.value(), - std::string_view("token"), - ServerOptions.UpstreamCacheConfig.HordeConfig.AccessToken); - UpdateStringValueFromConfig(HordeConfig.value(), - std::string_view("cluster"), - ServerOptions.UpstreamCacheConfig.HordeConfig.Cluster); - UpdateStringValueFromConfig(HordeConfig.value(), - std::string_view("namespace"), - ServerOptions.UpstreamCacheConfig.HordeConfig.Namespace); - }; - - if (auto StorageConfig = UpstreamConfig->get<sol::optional<sol::table>>("storage")) - { - UpdateStringValueFromConfig(StorageConfig.value(), - std::string_view("url"), - ServerOptions.UpstreamCacheConfig.HordeConfig.StorageUrl); - UpdateStringValueFromConfig(StorageConfig.value(), - std::string_view("oauthprovider"), - ServerOptions.UpstreamCacheConfig.HordeConfig.StorageOAuthUrl); - UpdateStringValueFromConfig(StorageConfig.value(), - std::string_view("oauthclientid"), - ServerOptions.UpstreamCacheConfig.HordeConfig.StorageOAuthClientId); - UpdateStringValueFromConfig(StorageConfig.value(), - std::string_view("oauthclientsecret"), - ServerOptions.UpstreamCacheConfig.HordeConfig.StorageOAuthClientSecret); - UpdateStringValueFromConfig(StorageConfig.value(), - std::string_view("openidprovider"), - ServerOptions.UpstreamCacheConfig.HordeConfig.StorageOpenIdProvider); - UpdateStringValueFromConfig(StorageConfig.value(), - std::string_view("token"), - ServerOptions.UpstreamCacheConfig.HordeConfig.StorageAccessToken); - }; - } - } - - if (sol::optional<sol::table> GcConfig = lua["gc"]) - { - ServerOptions.GcConfig.MonitorIntervalSeconds = GcConfig.value().get_or("monitorintervalseconds", 30); - ServerOptions.GcConfig.IntervalSeconds = GcConfig.value().get_or("intervalseconds", 0); - ServerOptions.GcConfig.DiskReserveSize = GcConfig.value().get_or("diskreservesize", uint64_t(1u << 28)); - ServerOptions.GcConfig.DiskSizeSoftLimit = GcConfig.value().get_or("disksizesoftlimit", 0); - ServerOptions.GcConfig.MinimumFreeDiskSpaceToAllowWrites = GcConfig.value().get_or("lowdiskspacethreshold", 0); - - if (sol::optional<sol::table> CacheGcConfig = GcConfig.value()["cache"]) - { - ServerOptions.GcConfig.Cache.MaxDurationSeconds = CacheGcConfig.value().get_or("maxdurationseconds", int32_t(0)); - } - if (sol::optional<sol::table> CacheGcConfig = GcConfig.value()["projectstore"]) - { - ServerOptions.GcConfig.ProjectStore.MaxDurationSeconds = CacheGcConfig.value().get_or("maxdurationseconds", int32_t(0)); - } - } - - if (sol::optional<sol::table> SecurityConfig = lua["security"]) - { - if (sol::optional<sol::table> OpenIdProviders = SecurityConfig.value()["openidproviders"]) - { - for (const auto& Kv : OpenIdProviders.value()) - { - if (sol::optional<sol::table> OpenIdProvider = Kv.second.as<sol::table>()) - { - std::string Name = OpenIdProvider.value().get_or("name", std::string("Default")); - std::string Url = OpenIdProvider.value().get_or("url", std::string()); - std::string ClientId = OpenIdProvider.value().get_or("clientid", std::string()); - - ServerOptions.AuthConfig.OpenIdProviders.push_back( - {.Name = std::move(Name), .Url = std::move(Url), .ClientId = std::move(ClientId)}); - } - } - } - - ServerOptions.EncryptionKey = SecurityConfig.value().get_or("encryptionaeskey", std::string()); - ServerOptions.EncryptionIV = SecurityConfig.value().get_or("encryptionaesiv", std::string()); - } - } -} |