Compare commits

...

6 Commits

Author SHA1 Message Date
dijunkun
e09243f1ec [chore] update README 2025-12-10 00:23:04 +08:00
dijunkun
f5941c7eda [chore] update README 2025-12-10 00:20:26 +08:00
dijunkun
3c2ebe602e [fix] do not save password info into logs 2025-12-10 00:20:10 +08:00
dijunkun
2f64172ead [feat] use new client id and password if server switched 2025-12-10 00:09:49 +08:00
Junkun Di
a83206a346 [ci] update issue templates 2025-12-09 17:50:57 +08:00
dijunkun
46e769976f [chore] update README 2025-12-09 00:36:00 +08:00
9 changed files with 357 additions and 62 deletions

View File

@@ -1,6 +1,6 @@
---
name: 问题反馈
about: Create a report to help us improve
about: 请在此提交问题报告,以便持续优化产品。
title: ''
labels: bug
assignees: kunkundi

28
.github/ISSUE_TEMPLATE/需求建议.md vendored Normal file
View File

@@ -0,0 +1,28 @@
---
name: 需求建议
about: 请在此提交功能需求或改进建议,以便后续迭代参考。
title: ''
labels: enhancement
assignees: kunkundi
---
**功能/改进建议描述**
清晰简洁地描述希望新增的功能或改进的内容。
**使用场景 / 背景**
说明该功能或改进的使用场景,以及解决后带来的价值。
**预期效果**
描述你认为最理想的功能表现或改进效果。
**参考示例(可选)**
提供类似功能截图、参考链接或其他说明,帮助更好理解需求。
**优先级(可选)**
- [ ]
- [ ]
- [ ]
**补充信息(可选)**
其他相关信息或特殊要求。

View File

@@ -169,7 +169,7 @@ xmake r -d crossdesk
## 自托管服务器
推荐使用Docker部署CrossDesk Server。
```
```bash
sudo docker run -d \
--name crossdesk_server \
--network host \
@@ -181,7 +181,7 @@ sudo docker run -d \
-e MAX_PORT=xxxxx \
-v /var/lib/crossdesk:/var/lib/crossdesk \
-v /var/log/crossdesk:/var/log/crossdesk \
crossdesk/crossdesk-server:v1.1.2
crossdesk/crossdesk-server:v1.1.3
```
上述命令中,用户需注意的参数如下:
@@ -194,9 +194,25 @@ sudo docker run -d \
- MIN_PORT/MAX_PORTCOTURN 服务使用的端口范围例如MIN_PORT=50000, MAX_PORT=60000范围可根据客户端数量调整。
- `-v /var/lib/crossdesk:/var/lib/crossdesk`:持久化数据库和证书文件到宿主机
- `-v /var/log/crossdesk:/var/log/crossdesk`:持久化日志文件到宿主机
-
**示例**
```bash
sudo docker run -d \
--name crossdesk_server \
--network host \
-e EXTERNAL_IP=114.114.114.114 \
-e INTERNAL_IP=10.0.0.1 \
-e CROSSDESK_SERVER_PORT=9099 \
-e COTURN_PORT=3478 \
-e MIN_PORT=50000 \
-e MAX_PORT=60000 \
-v /var/lib/crossdesk:/var/lib/crossdesk \
-v /var/log/crossdesk:/var/log/crossdesk \
crossdesk/crossdesk-server:v1.1.3
```
**注意**
- **服务器需开放端口:3478/udp3478/tcpMIN_PORT-MAX_PORT/udpCROSSDESK_SERVER_PORT/tcp。**
- **服务器需开放端口:COTURN_PORT/udpCOTURN_PORT/tcpMIN_PORT-MAX_PORT/udpCROSSDESK_SERVER_PORT/tcp。**
- 如果不挂载 volume容器删除后数据会丢失
- 证书文件会在首次启动时自动生成并持久化到宿主机的 `/var/lib/crossdesk/certs` 路径下
- 数据库文件会自动创建并持久化到宿主机的 `/var/lib/crossdesk/db/crossdesk-server.db` 路径下

View File

@@ -189,7 +189,7 @@ sudo docker run -d \
-e MAX_PORT=xxxxx \
-v /var/lib/crossdesk:/var/lib/crossdesk \
-v /var/log/crossdesk:/var/log/crossdesk \
crossdesk/crossdesk-server:v1.1.2
crossdesk/crossdesk-server:v1.1.3
```
The parameters you need to pay attention to are as follows:
@@ -203,8 +203,24 @@ The parameters you need to pay attention to are as follows:
- `-v /var/lib/crossdesk:/var/lib/crossdesk`: Persists database and certificate files on the host machine.
- `-v /var/log/crossdesk:/var/log/crossdesk`: Persists log files on the host machine.
**Example**:
```bash
sudo docker run -d \
--name crossdesk_server \
--network host \
-e EXTERNAL_IP=114.114.114.114 \
-e INTERNAL_IP=10.0.0.1 \
-e CROSSDESK_SERVER_PORT=9099 \
-e COTURN_PORT=3478 \
-e MIN_PORT=50000 \
-e MAX_PORT=60000 \
-v /var/lib/crossdesk:/var/lib/crossdesk \
-v /var/log/crossdesk:/var/log/crossdesk \
crossdesk/crossdesk-server:v1.1.3
```
**Notes**
- **The server must open the following ports: 3478/udp, 3478/tcp, MIN_PORTMAX_PORT/udp, and CROSSDESK_SERVER_PORT/tcp.**
- **The server must open the following ports: COTURN_PORT/udp, COTURN_PORT/tcp, MIN_PORTMAX_PORT/udp, and CROSSDESK_SERVER_PORT/tcp.**
- If you dont mount volumes, all data will be lost when the container is removed.
- Certificate files will be automatically generated on first startup and persisted to the host at `/var/lib/crossdesk/certs`.
- The database file will be automatically created and stored at `/var/lib/crossdesk/db/crossdesk-server.db`.

View File

@@ -233,15 +233,41 @@ int Render::LocalWindow() {
sizeof(password_saved_) - 1);
password_saved_[sizeof(password_saved_) - 1] = '\0';
// if self hosted
if (config_center_->IsSelfHosted()) {
std::string self_hosted_id_str;
if (strlen(self_hosted_id_) > 0) {
const char* at_pos = strchr(self_hosted_id_, '@');
if (at_pos != nullptr) {
self_hosted_id_str =
std::string(self_hosted_id_, at_pos - self_hosted_id_);
} else {
self_hosted_id_str = self_hosted_id_;
}
} else {
self_hosted_id_str = client_id_;
}
std::string new_self_hosted_id =
self_hosted_id_str + "@" + password_saved_;
memset(&self_hosted_id_, 0, sizeof(self_hosted_id_));
strncpy(self_hosted_id_, new_self_hosted_id.c_str(),
sizeof(self_hosted_id_) - 1);
self_hosted_id_[sizeof(self_hosted_id_) - 1] = '\0';
} else {
std::string client_id_with_password =
std::string(client_id_) + "@" + password_saved_;
strncpy(client_id_with_password_, client_id_with_password.c_str(),
sizeof(client_id_with_password_) - 1);
client_id_with_password_[sizeof(client_id_with_password_) - 1] =
'\0';
}
SaveSettingsIntoCacheFile();
memset(new_password_, 0, sizeof(new_password_));
LeaveConnection(peer_, client_id_);
DestroyPeer(&peer_);
focus_on_input_widget_ = true;

View File

@@ -203,22 +203,41 @@ Render::~Render() {}
int Render::SaveSettingsIntoCacheFile() {
cd_cache_mutex_.lock();
std::ofstream cd_cache_file(cache_path_ + "/secure_cache.enc",
std::ofstream cd_cache_v2_file(cache_path_ + "/secure_cache_v2.enc",
std::ios::binary);
if (!cd_cache_file) {
if (!cd_cache_v2_file) {
cd_cache_mutex_.unlock();
return -1;
}
memset(&cd_cache_v2_.client_id_with_password, 0,
sizeof(cd_cache_v2_.client_id_with_password));
memcpy(cd_cache_v2_.client_id_with_password, client_id_with_password_,
sizeof(client_id_with_password_));
memcpy(&cd_cache_v2_.key, &aes128_key_, sizeof(aes128_key_));
memcpy(&cd_cache_v2_.iv, &aes128_iv_, sizeof(aes128_iv_));
memset(&cd_cache_v2_.self_hosted_id, 0, sizeof(cd_cache_v2_.self_hosted_id));
memcpy(cd_cache_v2_.self_hosted_id, self_hosted_id_, sizeof(self_hosted_id_));
cd_cache_v2_file.write(reinterpret_cast<char*>(&cd_cache_v2_),
sizeof(CDCacheV2));
cd_cache_v2_file.close();
std::ofstream cd_cache_file(cache_path_ + "/secure_cache.enc",
std::ios::binary);
if (cd_cache_file) {
memset(&cd_cache_.client_id_with_password, 0,
sizeof(cd_cache_.client_id_with_password));
memcpy(cd_cache_.client_id_with_password, client_id_with_password_,
sizeof(client_id_with_password_));
memcpy(&cd_cache_.key, &aes128_key_, sizeof(aes128_key_));
memcpy(&cd_cache_.iv, &aes128_iv_, sizeof(aes128_iv_));
cd_cache_file.write(reinterpret_cast<char*>(&cd_cache_), sizeof(CDCache));
cd_cache_file.close();
}
cd_cache_mutex_.unlock();
return 0;
@@ -226,6 +245,29 @@ int Render::SaveSettingsIntoCacheFile() {
int Render::LoadSettingsFromCacheFile() {
cd_cache_mutex_.lock();
std::ifstream cd_cache_v2_file(cache_path_ + "/secure_cache_v2.enc",
std::ios::binary);
bool v2_file_exists = cd_cache_v2_file.good();
if (v2_file_exists) {
cd_cache_v2_file.read(reinterpret_cast<char*>(&cd_cache_v2_),
sizeof(CDCacheV2));
cd_cache_v2_file.close();
memset(&client_id_with_password_, 0, sizeof(client_id_with_password_));
memcpy(client_id_with_password_, cd_cache_v2_.client_id_with_password,
sizeof(client_id_with_password_));
memset(&self_hosted_id_, 0, sizeof(self_hosted_id_));
memcpy(self_hosted_id_, cd_cache_v2_.self_hosted_id,
sizeof(self_hosted_id_));
memcpy(aes128_key_, cd_cache_v2_.key, sizeof(cd_cache_v2_.key));
memcpy(aes128_iv_, cd_cache_v2_.iv, sizeof(cd_cache_v2_.iv));
LOG_INFO("Load settings from v2 cache file");
} else {
std::ifstream cd_cache_file(cache_path_ + "/secure_cache.enc",
std::ios::binary);
if (!cd_cache_file) {
@@ -234,6 +276,7 @@ int Render::LoadSettingsFromCacheFile() {
memset(password_saved_, 0, sizeof(password_saved_));
memset(aes128_key_, 0, sizeof(aes128_key_));
memset(aes128_iv_, 0, sizeof(aes128_iv_));
memset(self_hosted_id_, 0, sizeof(self_hosted_id_));
thumbnail_.reset();
thumbnail_ = std::make_shared<Thumbnail>(cache_path_ + "/thumbnails/");
@@ -247,12 +290,36 @@ int Render::LoadSettingsFromCacheFile() {
cd_cache_file.read(reinterpret_cast<char*>(&cd_cache_), sizeof(CDCache));
cd_cache_file.close();
cd_cache_mutex_.unlock();
memset(&cd_cache_v2_.client_id_with_password, 0,
sizeof(cd_cache_v2_.client_id_with_password));
memcpy(cd_cache_v2_.client_id_with_password,
cd_cache_.client_id_with_password,
sizeof(cd_cache_.client_id_with_password));
memcpy(&cd_cache_v2_.key, &cd_cache_.key, sizeof(cd_cache_.key));
memcpy(&cd_cache_v2_.iv, &cd_cache_.iv, sizeof(cd_cache_.iv));
memset(&cd_cache_v2_.self_hosted_id, 0,
sizeof(cd_cache_v2_.self_hosted_id));
memset(&client_id_with_password_, 0, sizeof(client_id_with_password_));
memcpy(client_id_with_password_, cd_cache_.client_id_with_password,
sizeof(client_id_with_password_));
memset(&self_hosted_id_, 0, sizeof(self_hosted_id_));
memcpy(aes128_key_, cd_cache_.key, sizeof(cd_cache_.key));
memcpy(aes128_iv_, cd_cache_.iv, sizeof(cd_cache_.iv));
cd_cache_mutex_.unlock();
SaveSettingsIntoCacheFile();
cd_cache_mutex_.lock();
LOG_INFO("Migrated settings from v1 to v2 cache file");
}
cd_cache_mutex_.unlock();
if (strchr(client_id_with_password_, '@') != nullptr) {
std::string id, password;
const char* at_pos = strchr(client_id_with_password_, '@');
@@ -273,9 +340,6 @@ int Render::LoadSettingsFromCacheFile() {
password_saved_[sizeof(password_saved_) - 1] = '\0';
}
memcpy(aes128_key_, cd_cache_.key, sizeof(cd_cache_.key));
memcpy(aes128_iv_, cd_cache_.iv, sizeof(cd_cache_.iv));
thumbnail_.reset();
thumbnail_ = std::make_shared<Thumbnail>(cache_path_ + "/thumbnails/",
aes128_key_, aes128_iv_);
@@ -480,11 +544,68 @@ int Render::CreateConnectionPeer() {
signal_server_port = config_center_->GetSignalServerPort();
coturn_server_port = config_center_->GetCoturnServerPort();
tls_cert_path = config_center_->GetCertFilePath();
std::string current_self_hosted_ip = config_center_->GetSignalServerHost();
bool use_cached_id = false;
// Check secure_cache_v2.enc exists or not
std::ifstream v2_file(cache_path_ + "/secure_cache_v2.enc",
std::ios::binary);
if (v2_file.good()) {
CDCacheV2 temp_cache;
v2_file.read(reinterpret_cast<char*>(&temp_cache), sizeof(CDCacheV2));
v2_file.close();
if (strlen(temp_cache.self_hosted_id) > 0) {
memset(&self_hosted_id_, 0, sizeof(self_hosted_id_));
strncpy(self_hosted_id_, temp_cache.self_hosted_id,
sizeof(self_hosted_id_) - 1);
self_hosted_id_[sizeof(self_hosted_id_) - 1] = '\0';
use_cached_id = true;
std::string id, password;
const char* at_pos = strchr(self_hosted_id_, '@');
if (at_pos == nullptr) {
id = self_hosted_id_;
password.clear();
} else {
id.assign(self_hosted_id_, at_pos - self_hosted_id_);
password = at_pos + 1;
}
memset(&client_id_, 0, sizeof(client_id_));
strncpy(client_id_, id.c_str(), sizeof(client_id_) - 1);
client_id_[sizeof(client_id_) - 1] = '\0';
memset(&password_saved_, 0, sizeof(password_saved_));
strncpy(password_saved_, password.c_str(), sizeof(password_saved_) - 1);
password_saved_[sizeof(password_saved_) - 1] = '\0';
}
} else {
memset(&self_hosted_id_, 0, sizeof(self_hosted_id_));
LOG_INFO(
"secure_cache_v2.enc not found, will use empty id to get new id from "
"server");
}
if (use_cached_id && strlen(self_hosted_id_) > 0) {
memset(&self_hosted_user_id_, 0, sizeof(self_hosted_user_id_));
strncpy(self_hosted_user_id_, self_hosted_id_,
sizeof(self_hosted_user_id_) - 1);
self_hosted_user_id_[sizeof(self_hosted_user_id_) - 1] = '\0';
params_.user_id = self_hosted_user_id_;
} else {
memset(&self_hosted_user_id_, 0, sizeof(self_hosted_user_id_));
params_.user_id = self_hosted_user_id_;
LOG_INFO(
"Using empty id for self-hosted server, server will assign new id");
}
} else {
signal_server_ip = config_center_->GetDefaultServerHost();
signal_server_port = config_center_->GetDefaultSignalServerPort();
coturn_server_port = config_center_->GetDefaultCoturnServerPort();
tls_cert_path = config_center_->GetDefaultCertFilePath();
params_.user_id = client_id_with_password_;
}
// self hosted server config
@@ -554,7 +675,6 @@ int Render::CreateConnectionPeer() {
params_.on_connection_status = OnConnectionStatusCb;
params_.net_status_report = NetStatusReport;
params_.user_id = client_id_with_password_;
params_.user_data = this;
peer_ = CreatePeer(&params_);

View File

@@ -277,8 +277,25 @@ class Render {
unsigned char iv[16];
};
struct CDCacheV2 {
char client_id_with_password[17];
int language;
int video_quality;
int video_frame_rate;
int video_encode_format;
bool enable_hardware_video_codec;
bool enable_turn;
bool enable_srtp;
unsigned char key[16];
unsigned char iv[16];
char self_hosted_id[17];
};
private:
CDCache cd_cache_;
CDCacheV2 cd_cache_v2_;
std::mutex cd_cache_mutex_;
std::unique_ptr<ConfigCenter> config_center_;
ConfigCenter::LANGUAGE localization_language_ =
@@ -470,6 +487,8 @@ class Render {
char client_id_display_[12] = "";
char client_id_with_password_[17] = "";
char password_saved_[7] = "";
char self_hosted_id_[17] = "";
char self_hosted_user_id_[17] = "";
int language_button_value_ = 0;
int video_quality_button_value_ = 0;
int video_frame_rate_button_value_ = 1;

View File

@@ -1,4 +1,5 @@
#include <cmath>
#include <fstream>
#include "device_controller.h"
#include "localization.h"
@@ -556,6 +557,73 @@ void Render::NetStatusReport(const char* client_id, size_t client_id_size,
password = at_pos + 1;
}
bool is_self_hosted = render->config_center_->IsSelfHosted();
if (is_self_hosted) {
memset(&render->client_id_, 0, sizeof(render->client_id_));
strncpy(render->client_id_, id.c_str(), sizeof(render->client_id_) - 1);
render->client_id_[sizeof(render->client_id_) - 1] = '\0';
memset(&render->password_saved_, 0, sizeof(render->password_saved_));
strncpy(render->password_saved_, password.c_str(),
sizeof(render->password_saved_) - 1);
render->password_saved_[sizeof(render->password_saved_) - 1] = '\0';
memset(&render->self_hosted_id_, 0, sizeof(render->self_hosted_id_));
strncpy(render->self_hosted_id_, client_id,
sizeof(render->self_hosted_id_) - 1);
render->self_hosted_id_[sizeof(render->self_hosted_id_) - 1] = '\0';
LOG_INFO("Use self-hosted client id [{}] and save to cache file", id);
render->cd_cache_mutex_.lock();
std::ifstream v2_file_read(render->cache_path_ + "/secure_cache_v2.enc",
std::ios::binary);
if (v2_file_read.good()) {
v2_file_read.read(reinterpret_cast<char*>(&render->cd_cache_v2_),
sizeof(CDCacheV2));
v2_file_read.close();
} else {
memset(&render->cd_cache_v2_, 0, sizeof(CDCacheV2));
memset(&render->cd_cache_v2_.client_id_with_password, 0,
sizeof(render->cd_cache_v2_.client_id_with_password));
strncpy(render->cd_cache_v2_.client_id_with_password,
render->client_id_with_password_,
sizeof(render->cd_cache_v2_.client_id_with_password));
memcpy(&render->cd_cache_v2_.key, &render->aes128_key_,
sizeof(render->cd_cache_v2_.key));
memcpy(&render->cd_cache_v2_.iv, &render->aes128_iv_,
sizeof(render->cd_cache_v2_.iv));
}
memset(&render->cd_cache_v2_.self_hosted_id, 0,
sizeof(render->cd_cache_v2_.self_hosted_id));
strncpy(render->cd_cache_v2_.self_hosted_id, client_id,
sizeof(render->cd_cache_v2_.self_hosted_id) - 1);
render->cd_cache_v2_
.self_hosted_id[sizeof(render->cd_cache_v2_.self_hosted_id) - 1] =
'\0';
memset(&render->cd_cache_v2_.client_id_with_password, 0,
sizeof(render->cd_cache_v2_.client_id_with_password));
strncpy(render->cd_cache_v2_.client_id_with_password,
render->client_id_with_password_,
sizeof(render->cd_cache_v2_.client_id_with_password));
memcpy(&render->cd_cache_v2_.key, &render->aes128_key_,
sizeof(render->cd_cache_v2_.key));
memcpy(&render->cd_cache_v2_.iv, &render->aes128_iv_,
sizeof(render->cd_cache_v2_.iv));
std::ofstream cd_cache_v2_file(
render->cache_path_ + "/secure_cache_v2.enc", std::ios::binary);
if (cd_cache_v2_file) {
cd_cache_v2_file.write(reinterpret_cast<char*>(&render->cd_cache_v2_),
sizeof(CDCacheV2));
cd_cache_v2_file.close();
}
render->cd_cache_mutex_.unlock();
} else {
memset(&render->client_id_, 0, sizeof(render->client_id_));
strncpy(render->client_id_, id.c_str(), sizeof(render->client_id_) - 1);
render->client_id_[sizeof(render->client_id_) - 1] = '\0';
@@ -569,12 +637,14 @@ void Render::NetStatusReport(const char* client_id, size_t client_id_size,
sizeof(render->client_id_with_password_));
strncpy(render->client_id_with_password_, client_id,
sizeof(render->client_id_with_password_) - 1);
render->client_id_with_password_[sizeof(render->client_id_with_password_) -
render
->client_id_with_password_[sizeof(render->client_id_with_password_) -
1] = '\0';
LOG_INFO("Use client id [{}] and save id into cache file", id);
render->SaveSettingsIntoCacheFile();
}
}
std::string remote_id(user_id, user_id_size);
// std::shared_lock lock(render->client_properties_mutex_);