Files
obs-websocket/src/Config.cpp
tt2468 6c9fd55c63 config: Always write config when migrating
Fixes an issue where OBS 30.1.2 migrations would work on the first
30.2.0 load, but the settings would not persist to disk for further
loads.
2024-07-17 22:58:54 -07:00

228 lines
9.0 KiB
C++

/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <filesystem>
#include <obs-frontend-api.h>
#include "Config.h"
#include "utils/Crypto.h"
#include "utils/Platform.h"
#include "utils/Obs.h"
#define CONFIG_SECTION_NAME "OBSWebSocket"
#define CONFIG_PARAM_FIRSTLOAD "FirstLoad"
#define CONFIG_PARAM_ENABLED "ServerEnabled"
#define CONFIG_PARAM_PORT "ServerPort"
#define CONFIG_PARAM_ALERTS "AlertsEnabled"
#define CONFIG_PARAM_AUTHREQUIRED "AuthRequired"
#define CONFIG_PARAM_PASSWORD "ServerPassword"
#define CONFIG_FILE_NAME "config.json"
#define PARAM_FIRSTLOAD "first_load"
#define PARAM_ENABLED "server_enabled"
#define PARAM_PORT "server_port"
#define PARAM_ALERTS "alerts_enabled"
#define PARAM_AUTHREQUIRED "auth_required"
#define PARAM_PASSWORD "server_password"
#define CMDLINE_WEBSOCKET_PORT "websocket_port"
#define CMDLINE_WEBSOCKET_IPV4_ONLY "websocket_ipv4_only"
#define CMDLINE_WEBSOCKET_PASSWORD "websocket_password"
#define CMDLINE_WEBSOCKET_DEBUG "websocket_debug"
void Config::Load(json config)
{
// Only load from plugin config directory if there hasn't been a migration
if (config.is_null()) {
std::string configFilePath = Utils::Obs::StringHelper::GetModuleConfigPath(CONFIG_FILE_NAME);
Utils::Json::GetJsonFileContent(configFilePath, config); // Fetch the existing config, which may not exist
}
if (!config.is_object()) {
blog(LOG_INFO, "[Config::Load] Existing configuration not found, using defaults.");
config = json::object();
}
if (config.contains(PARAM_FIRSTLOAD) && config[PARAM_FIRSTLOAD].is_boolean())
FirstLoad = config[PARAM_FIRSTLOAD];
if (config.contains(PARAM_ENABLED) && config[PARAM_ENABLED].is_boolean())
ServerEnabled = config[PARAM_ENABLED];
if (config.contains(PARAM_ALERTS) && config[PARAM_ALERTS].is_boolean())
AlertsEnabled = config[PARAM_ALERTS];
if (config.contains(PARAM_PORT) && config[PARAM_PORT].is_number_unsigned())
ServerPort = config[PARAM_PORT];
if (config.contains(PARAM_AUTHREQUIRED) && config[PARAM_AUTHREQUIRED].is_boolean())
AuthRequired = config[PARAM_AUTHREQUIRED];
if (config.contains(PARAM_PASSWORD) && config[PARAM_PASSWORD].is_string())
ServerPassword = config[PARAM_PASSWORD];
// Set server password and save it to the config before processing overrides,
// so that there is always a true configured password regardless of if
// future loads use the override flag.
if (FirstLoad) {
FirstLoad = false;
if (ServerPassword.empty()) {
blog(LOG_INFO, "[Config::Load] (FirstLoad) Generating new server password.");
ServerPassword = Utils::Crypto::GeneratePassword();
} else {
blog(LOG_INFO, "[Config::Load] (FirstLoad) Not generating new password since one is already configured.");
}
Save();
}
// If there are migrated settings, write them to disk before processing arguments.
if (!config.empty())
Save();
// Process `--websocket_port` override
QString portArgument = Utils::Platform::GetCommandLineArgument(CMDLINE_WEBSOCKET_PORT);
if (portArgument != "") {
bool ok;
uint16_t serverPort = portArgument.toUShort(&ok);
if (ok) {
blog(LOG_INFO, "[Config::Load] --websocket_port passed. Overriding WebSocket port with: %d", serverPort);
PortOverridden = true;
ServerPort = serverPort;
} else {
blog(LOG_WARNING, "[Config::Load] Not overriding WebSocket port since integer conversion failed.");
}
}
// Process `--websocket_ipv4_only` override
if (Utils::Platform::GetCommandLineFlagSet(CMDLINE_WEBSOCKET_IPV4_ONLY)) {
blog(LOG_INFO, "[Config::Load] --websocket_ipv4_only passed. Binding only to IPv4 interfaces.");
Ipv4Only = true;
}
// Process `--websocket_password` override
QString passwordArgument = Utils::Platform::GetCommandLineArgument(CMDLINE_WEBSOCKET_PASSWORD);
if (passwordArgument != "") {
blog(LOG_INFO, "[Config::Load] --websocket_password passed. Overriding WebSocket password.");
PasswordOverridden = true;
AuthRequired = true;
ServerPassword = passwordArgument.toStdString();
}
// Process `--websocket_debug` override
if (Utils::Platform::GetCommandLineFlagSet(CMDLINE_WEBSOCKET_DEBUG)) {
// Debug does not persist on reload, so we let people override it with a flag.
blog(LOG_INFO, "[Config::Load] --websocket_debug passed. Enabling debug logging.");
DebugEnabled = true;
}
}
void Config::Save()
{
json config;
std::string configFilePath = Utils::Obs::StringHelper::GetModuleConfigPath(CONFIG_FILE_NAME);
Utils::Json::GetJsonFileContent(configFilePath, config); // Fetch the existing config, which may not exist
config[PARAM_FIRSTLOAD] = FirstLoad.load();
config[PARAM_ENABLED] = ServerEnabled.load();
if (!PortOverridden)
config[PARAM_PORT] = ServerPort.load();
config[PARAM_ALERTS] = AlertsEnabled.load();
if (!PasswordOverridden) {
config[PARAM_AUTHREQUIRED] = AuthRequired.load();
config[PARAM_PASSWORD] = ServerPassword;
}
if (Utils::Json::SetJsonFileContent(configFilePath, config))
blog(LOG_DEBUG, "[Config::Save] Saved config.");
else
blog(LOG_ERROR, "[Config::Save] Failed to write config file!");
}
// Finds any old values in global.ini and removes them, then returns the values as JSON
json MigrateGlobalConfigData()
{
// Get existing global config
config_t *config = obs_frontend_get_global_config();
json ret;
// Move values to temporary JSON blob
if (config_has_user_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_FIRSTLOAD)) {
ret[PARAM_FIRSTLOAD] = config_get_bool(config, CONFIG_SECTION_NAME, CONFIG_PARAM_FIRSTLOAD);
config_remove_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_FIRSTLOAD);
}
if (config_has_user_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_ENABLED)) {
ret[PARAM_ENABLED] = config_get_bool(config, CONFIG_SECTION_NAME, CONFIG_PARAM_ENABLED);
config_remove_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_ENABLED);
}
if (config_has_user_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_PORT)) {
ret[PARAM_PORT] = config_get_uint(config, CONFIG_SECTION_NAME, CONFIG_PARAM_PORT);
config_remove_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_PORT);
}
if (config_has_user_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_ALERTS)) {
ret[PARAM_ALERTS] = config_get_bool(config, CONFIG_SECTION_NAME, CONFIG_PARAM_ALERTS);
config_remove_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_ALERTS);
}
if (config_has_user_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_AUTHREQUIRED)) {
ret[PARAM_AUTHREQUIRED] = config_get_bool(config, CONFIG_SECTION_NAME, CONFIG_PARAM_AUTHREQUIRED);
config_remove_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_AUTHREQUIRED);
}
if (config_has_user_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_PASSWORD)) {
ret[PARAM_PASSWORD] = config_get_string(config, CONFIG_SECTION_NAME, CONFIG_PARAM_PASSWORD);
config_remove_value(config, CONFIG_SECTION_NAME, CONFIG_PARAM_PASSWORD);
}
if (!ret.is_null()) {
blog(LOG_INFO, "[MigrateGlobalConfigData] Some configurations have been migrated from old config");
config_save(config);
}
return ret;
}
// Migration from storing persistent data in obsWebSocketPersistentData.json to the module config directory
// This will overwrite any persistent data in the destination. People doing manual OBS config modification be warned!
bool MigratePersistentData()
{
std::error_code ec;
// Ensure module config directory exists
auto moduleConfigDirectory = std::filesystem::u8path(Utils::Obs::StringHelper::GetModuleConfigPath(""));
if (!std::filesystem::exists(moduleConfigDirectory, ec))
std::filesystem::create_directories(moduleConfigDirectory, ec);
if (ec) {
blog(LOG_ERROR, "[MigratePersistentData] Failed to create directory `%s`: %s", moduleConfigDirectory.c_str(),
ec.message().c_str());
return false;
}
// Move any existing persistent data to module config directory, then delete old file
auto oldPersistentDataPath = std::filesystem::u8path(Utils::Obs::StringHelper::GetCurrentProfilePath() +
"/../../../obsWebSocketPersistentData.json");
if (std::filesystem::exists(oldPersistentDataPath, ec)) {
auto persistentDataPath =
std::filesystem::u8path(Utils::Obs::StringHelper::GetModuleConfigPath("persistent_data.json"));
std::filesystem::copy_file(oldPersistentDataPath, persistentDataPath, ec);
std::filesystem::remove(oldPersistentDataPath, ec);
blog(LOG_INFO, "[MigratePersistentData] Persistent data migrated to new path");
}
if (ec) {
blog(LOG_ERROR, "[MigratePersistentData] Failed to move persistent data: %s", ec.message().c_str());
return false;
}
return true;
}