prevent overlapping api ports

This commit is contained in:
Scott E. Graves 2025-03-22 15:27:50 -05:00
parent e3d036fcb8
commit 630c3463d8
6 changed files with 119 additions and 132 deletions

View File

@ -43,8 +43,7 @@ public:
[[nodiscard]] static auto default_remote_api_port(const provider_type &prov) [[nodiscard]] static auto default_remote_api_port(const provider_type &prov)
-> std::uint16_t; -> std::uint16_t;
[[nodiscard]] static auto default_rpc_port(const provider_type &prov) [[nodiscard]] static auto default_rpc_port() -> std::uint16_t;
-> std::uint16_t;
[[nodiscard]] static auto get_provider_display_name(const provider_type &prov) [[nodiscard]] static auto get_provider_display_name(const provider_type &prov)
-> std::string; -> std::string;

View File

@ -67,7 +67,7 @@ app_config::app_config(const provider_type &prov,
std::string_view data_directory) std::string_view data_directory)
: prov_(prov), : prov_(prov),
api_password_(utils::generate_random_string(default_api_password_size)), api_password_(utils::generate_random_string(default_api_password_size)),
api_port_(default_rpc_port(prov)), api_port_(default_rpc_port()),
api_user_(std::string{REPERTORY}), api_user_(std::string{REPERTORY}),
config_changed_(false), config_changed_(false),
download_timeout_secs_(default_download_timeout_secs), download_timeout_secs_(default_download_timeout_secs),
@ -743,17 +743,7 @@ auto app_config::default_remote_api_port(const provider_type &prov)
return PROVIDER_REMOTE_PORTS.at(static_cast<std::size_t>(prov)); return PROVIDER_REMOTE_PORTS.at(static_cast<std::size_t>(prov));
} }
auto app_config::default_rpc_port(const provider_type &prov) -> std::uint16_t { auto app_config::default_rpc_port() -> std::uint16_t { return 10000U; }
static const std::array<std::uint16_t,
static_cast<std::size_t>(provider_type::unknown)>
PROVIDER_RPC_PORTS = {
10000U,
10010U,
10100U,
10002U,
};
return PROVIDER_RPC_PORTS.at(static_cast<std::size_t>(prov));
}
auto app_config::get_api_password() const -> std::string { auto app_config::get_api_password() const -> std::string {
return api_password_; return api_password_;

View File

@ -29,8 +29,6 @@
#include "types/repertory.hpp" #include "types/repertory.hpp"
#include "utils/cli_utils.hpp" #include "utils/cli_utils.hpp"
#include "utils/com_init_wrapper.hpp" #include "utils/com_init_wrapper.hpp"
#include "utils/file_utils.hpp"
#include "utils/string.hpp"
#if defined(_WIN32) #if defined(_WIN32)
#include "drives/winfsp/remotewinfsp/remote_client.hpp" #include "drives/winfsp/remotewinfsp/remote_client.hpp"
@ -62,102 +60,95 @@ mount(std::vector<const char *> args, std::string data_directory,
lock_data lock(prov, unique_id); lock_data lock(prov, unique_id);
const auto res = lock.grab_lock(); const auto res = lock.grab_lock();
if (res == lock_result::locked) { if (res == lock_result::locked) {
ret = exit_code::mount_active;
std::cerr << app_config::get_provider_display_name(prov) std::cerr << app_config::get_provider_display_name(prov)
<< " mount is already active" << std::endl; << " mount is already active" << std::endl;
} else if (res == lock_result::success) { return exit_code::mount_active;
const auto generate_config = utils::cli::has_option( }
args, utils::cli::options::generate_config_option);
if (generate_config) { if (res != lock_result::success) {
app_config config(prov, data_directory); ret = exit_code::lock_failed;
if (prov == provider_type::remote) {
auto cfg = config.get_remote_config();
cfg.host_name_or_ip = remote_host;
cfg.api_port = remote_port;
config.set_remote_config(cfg);
} else if (prov == provider_type::sia &&
config.get_sia_config().bucket.empty()) {
[[maybe_unused]] auto bucket =
config.set_value_by_name("SiaConfig.Bucket", unique_id);
} }
std::cout << "Generated " << app_config::get_provider_display_name(prov)
<< " Configuration" << std::endl;
std::cout << config.get_config_file_path() << std::endl;
ret = utils::file::file(config.get_config_file_path()).exists()
? exit_code::success
: exit_code::file_creation_failed;
} else {
#if defined(_WIN32) #if defined(_WIN32)
if (utils::cli::has_option(args, utils::cli::options::hidden_option)) { if (utils::cli::has_option(args, utils::cli::options::hidden_option)) {
::ShowWindow(::GetConsoleWindow(), SW_HIDE); ::ShowWindow(::GetConsoleWindow(), SW_HIDE);
} }
#endif // defined(_WIN32) #endif // defined(_WIN32)
auto drive_args =
utils::cli::parse_drive_options(args, prov, data_directory); auto drive_args = utils::cli::parse_drive_options(args, prov, data_directory);
app_config config(prov, data_directory); app_config config(prov, data_directory);
{
std::uint16_t port{};
if (not utils::get_next_available_port(config.get_api_port(), port)) {
std::cerr << "FATAL: Unable to get available port" << std::endl;
return exit_code::startup_exception;
}
config.set_api_port(port);
}
#if defined(_WIN32) #if defined(_WIN32)
if (config.get_enable_mount_manager() && if (config.get_enable_mount_manager() && not utils::is_process_elevated()) {
not utils::is_process_elevated()) { utils::com_init_wrapper wrapper;
utils::com_init_wrapper cw;
if (not lock.set_mount_state(true, "elevating", -1)) { if (not lock.set_mount_state(true, "elevating", -1)) {
std::cerr << "failed to set mount state" << std::endl; std::cerr << "failed to set mount state" << std::endl;
} }
lock.release(); lock.release();
mount_result = utils::run_process_elevated(args); mount_result = utils::run_process_elevated(args);
lock_data lock2(prov, unique_id); lock_data prov_lock(prov, unique_id);
if (lock2.grab_lock() == lock_result::success) { if (prov_lock.grab_lock() == lock_result::success) {
if (not lock2.set_mount_state(false, "", -1)) { if (not prov_lock.set_mount_state(false, "", -1)) {
std::cerr << "failed to set mount state" << std::endl; std::cerr << "failed to set mount state" << std::endl;
} }
lock2.release(); prov_lock.release();
} }
return exit_code::mount_result; return exit_code::mount_result;
} }
#endif // defined(_WIN32) #endif // defined(_WIN32)
std::cout << "Initializing "
<< app_config::get_provider_display_name(prov) std::cout << "Initializing " << app_config::get_provider_display_name(prov)
<< (unique_id.empty() ? "" << (unique_id.empty() ? ""
: (prov == provider_type::remote) : (prov == provider_type::remote)
? " [" + remote_host + ':' + ? " [" + remote_host + ':' + std::to_string(remote_port) +
std::to_string(remote_port) + ']' ']'
: " [" + unique_id + ']') : " [" + unique_id + ']')
<< " Drive" << std::endl; << " Drive" << std::endl;
if (prov == provider_type::remote) { if (prov == provider_type::remote) {
std::uint16_t port{0U}; std::uint16_t port{};
if (utils::get_next_available_port(config.get_api_port(), port)) { if (not utils::get_next_available_port(config.get_remote_config().api_port,
auto cfg = config.get_remote_config(); port)) {
cfg.host_name_or_ip = remote_host; std::cerr << "FATAL: Unable to get available port" << std::endl;
cfg.api_port = remote_port; return exit_code::startup_exception;
config.set_remote_config(cfg); }
config.set_api_port(port);
auto remote_cfg = config.get_remote_config();
remote_cfg.host_name_or_ip = remote_host;
remote_cfg.api_port = remote_port;
config.set_remote_config(remote_cfg);
try { try {
remote_drive drive( remote_drive drive(
config, config,
[&config]() -> std::unique_ptr<remote_instance> { [&config]() -> std::unique_ptr<remote_instance> {
return std::unique_ptr<remote_instance>( return std::unique_ptr<remote_instance>(new remote_client(config));
new remote_client(config));
}, },
lock); lock);
if (not lock.set_mount_state(true, "", -1)) { if (not lock.set_mount_state(true, "", -1)) {
std::cerr << "failed to set mount state" << std::endl; std::cerr << "failed to set mount state" << std::endl;
} }
mount_result = drive.mount(drive_args); mount_result = drive.mount(drive_args);
ret = exit_code::mount_result; return exit_code::mount_result;
} catch (const std::exception &e) { } catch (const std::exception &e) {
std::cerr << "FATAL: " << e.what() << std::endl; std::cerr << "FATAL: " << e.what() << std::endl;
ret = exit_code::startup_exception;
} }
} else {
std::cerr << "FATAL: Unable to get available port" << std::endl; return exit_code::startup_exception;
ret = exit_code::startup_exception;
} }
} else {
if (prov == provider_type::sia && if (prov == provider_type::sia && config.get_sia_config().bucket.empty()) {
config.get_sia_config().bucket.empty()) {
[[maybe_unused]] auto bucket = [[maybe_unused]] auto bucket =
config.set_value_by_name("SiaConfig.Bucket", unique_id); config.set_value_by_name("SiaConfig.Bucket", unique_id);
} }
@ -169,18 +160,12 @@ mount(std::vector<const char *> args, std::string data_directory,
std::cerr << "failed to set mount state" << std::endl; std::cerr << "failed to set mount state" << std::endl;
} }
mount_result = drive.mount(drive_args); mount_result = drive.mount(drive_args);
ret = exit_code::mount_result; return exit_code::mount_result;
} catch (const std::exception &e) { } catch (const std::exception &e) {
std::cerr << "FATAL: " << e.what() << std::endl; std::cerr << "FATAL: " << e.what() << std::endl;
ret = exit_code::startup_exception;
}
}
}
} else {
ret = exit_code::lock_failed;
} }
return ret; return exit_code::startup_exception;
} }
} // namespace repertory::cli::actions } // namespace repertory::cli::actions

View File

@ -505,6 +505,9 @@ void handlers::handle_post_mount(const httplib::Request &req,
return; return;
} }
static std::mutex mount_mtx;
mutex_lock lock(mount_mtx);
launch_process(prov, name, {location}, true); launch_process(prov, name, {location}, true);
config_->set_mount_location(prov, name, location); config_->set_mount_location(prov, name, location);
} }

View File

@ -128,7 +128,7 @@ std::atomic<std::uint64_t> app_config_test::idx{0U};
static void defaults_tests(const json &json_data, provider_type prov) { static void defaults_tests(const json &json_data, provider_type prov) {
json json_defaults = { json json_defaults = {
{JSON_API_PORT, app_config::default_rpc_port(prov)}, {JSON_API_PORT, app_config::default_rpc_port()},
{JSON_API_USER, std::string{REPERTORY}}, {JSON_API_USER, std::string{REPERTORY}},
{JSON_DOWNLOAD_TIMEOUT_SECS, default_download_timeout_secs}, {JSON_DOWNLOAD_TIMEOUT_SECS, default_download_timeout_secs},
{JSON_DATABASE_TYPE, database_type::rocksdb}, {JSON_DATABASE_TYPE, database_type::rocksdb},

View File

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -111,10 +112,19 @@ class MountList with ChangeNotifier {
Future<bool> add( Future<bool> add(
String type, String type,
String name, String name,
Map<String, dynamic> mountConfig, Map<String, dynamic> settings,
) async { ) async {
var ret = false; var ret = false;
var apiPort = settings['ApiPort'] ?? 10000;
for (var mount in _mountList) {
var port = mount.mountConfig.settings['ApiPort'] as int?;
if (port != null) {
apiPort = max(apiPort, port + 1);
}
}
settings["ApiPort"] = apiPort;
displayError() { displayError() {
if (constants.navigatorKey.currentContext == null) { if (constants.navigatorKey.currentContext == null) {
return; return;
@ -129,7 +139,7 @@ class MountList with ChangeNotifier {
try { try {
final auth = await _auth.createAuth(); final auth = await _auth.createAuth();
final map = await convertAllToString( final map = await convertAllToString(
jsonDecode(jsonEncode(mountConfig)), jsonDecode(jsonEncode(settings)),
_auth.key, _auth.key,
); );
final response = await http.post( final response = await http.post(