mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-09 12:40:20 +08:00
feat(admin): configure S3 backup User-Agent
This commit is contained in:
@@ -11,6 +11,7 @@ pub(crate) struct S3BackupConfig {
|
||||
pub(crate) scope: BackupScope,
|
||||
pub(crate) endpoint: String,
|
||||
pub(crate) region: String,
|
||||
pub(crate) user_agent: String,
|
||||
pub(crate) bucket: String,
|
||||
pub(crate) prefix: String,
|
||||
pub(crate) access_key_id: String,
|
||||
@@ -104,6 +105,8 @@ impl S3BackupConfig {
|
||||
endpoint,
|
||||
region: optional_string(entries, "backup_s3_region")?
|
||||
.unwrap_or_else(|| "auto".to_string()),
|
||||
user_agent: optional_string(entries, "backup_s3_user_agent")?
|
||||
.unwrap_or_else(|| "rclone/v1.68.0".to_string()),
|
||||
bucket,
|
||||
prefix: optional_string(entries, "backup_s3_prefix")?
|
||||
.unwrap_or_else(|| "aether/backups/".to_string()),
|
||||
@@ -179,6 +182,7 @@ fn config_label(key: &str) -> &str {
|
||||
match key {
|
||||
"backup_s3_endpoint" => "Endpoint(S3 地址)",
|
||||
"backup_s3_region" => "Region(S3 区域)",
|
||||
"backup_s3_user_agent" => "User-Agent(请求头标识)",
|
||||
"backup_s3_bucket" => "Bucket(存储桶)",
|
||||
"backup_s3_prefix" => "Prefix(备份前缀)",
|
||||
"backup_s3_access_key_id" => "Access Key ID(访问密钥 ID)",
|
||||
@@ -384,6 +388,7 @@ mod tests {
|
||||
|
||||
assert_eq!(config.scope, BackupScope::Data);
|
||||
assert_eq!(config.region, "auto");
|
||||
assert_eq!(config.user_agent, "rclone/v1.68.0");
|
||||
assert_eq!(config.prefix, "aether/backups/");
|
||||
assert_eq!(config.path_style, true);
|
||||
assert_eq!(config.compression, "zstd");
|
||||
|
||||
@@ -132,6 +132,7 @@ mod tests {
|
||||
scope,
|
||||
endpoint: "https://example.com".to_string(),
|
||||
region: "auto".to_string(),
|
||||
user_agent: "rclone/v1.68.0".to_string(),
|
||||
bucket: "aether-backups".to_string(),
|
||||
prefix: "prod/".to_string(),
|
||||
access_key_id: "test-access-key".to_string(),
|
||||
|
||||
@@ -6,7 +6,8 @@ use bytes::Bytes;
|
||||
use futures_util::TryStreamExt;
|
||||
use object_store::aws::AmazonS3Builder;
|
||||
use object_store::path::Path;
|
||||
use object_store::ObjectStore;
|
||||
use object_store::{ClientOptions, ObjectStore};
|
||||
use reqwest::header::HeaderValue;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use super::config::S3BackupConfig;
|
||||
@@ -84,7 +85,19 @@ pub(crate) struct ObjectStoreS3BackupStore {
|
||||
|
||||
impl ObjectStoreS3BackupStore {
|
||||
pub(crate) fn from_config(config: &S3BackupConfig) -> Result<Self, BackupStoreError> {
|
||||
// 部分 S3 兼容网关(如中国科技云 s3.cstcloud.cn)按 User-Agent 放行请求,
|
||||
// object_store 默认 UA 会被拒,因此允许自定义 User-Agent。
|
||||
let client_options = if config.user_agent.trim().is_empty() {
|
||||
ClientOptions::new()
|
||||
} else {
|
||||
ClientOptions::new().with_user_agent(
|
||||
HeaderValue::from_str(config.user_agent.trim()).map_err(|error| {
|
||||
BackupStoreError::new(format!("S3 备份 User-Agent 配置无效: {error}"))
|
||||
})?,
|
||||
)
|
||||
};
|
||||
let store = AmazonS3Builder::new()
|
||||
.with_client_options(client_options)
|
||||
.with_endpoint(config.endpoint.clone())
|
||||
.with_region(config.region.clone())
|
||||
.with_bucket_name(config.bucket.clone())
|
||||
|
||||
@@ -31,6 +31,7 @@ const S3_BACKUP_CONFIG_KEYS: &[&str] = &[
|
||||
"backup_s3_scope",
|
||||
"backup_s3_endpoint",
|
||||
"backup_s3_region",
|
||||
"backup_s3_user_agent",
|
||||
"backup_s3_bucket",
|
||||
"backup_s3_prefix",
|
||||
"backup_s3_access_key_id",
|
||||
|
||||
@@ -1724,6 +1724,7 @@ pub fn admin_system_config_default_value(key: &str) -> Option<serde_json::Value>
|
||||
"backup_s3_scope" => Some(json!("data")),
|
||||
"backup_s3_endpoint" => Some(serde_json::Value::Null),
|
||||
"backup_s3_region" => Some(json!("auto")),
|
||||
"backup_s3_user_agent" => Some(json!("rclone/v1.68.0")),
|
||||
"backup_s3_bucket" => Some(serde_json::Value::Null),
|
||||
"backup_s3_prefix" => Some(json!("aether/backups/")),
|
||||
"backup_s3_access_key_id" => Some(serde_json::Value::Null),
|
||||
@@ -3378,6 +3379,10 @@ mod tests {
|
||||
admin_system_config_default_value("backup_s3_path_style"),
|
||||
Some(json!(true))
|
||||
);
|
||||
assert_eq!(
|
||||
admin_system_config_default_value("backup_s3_user_agent"),
|
||||
Some(json!("rclone/v1.68.0"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -964,6 +964,7 @@ export const MOCK_SYSTEM_CONFIGS: Array<{ key: string; value: unknown; descripti
|
||||
{ key: 'backup_s3_scope', value: 'data', description: 'S3 备份范围' },
|
||||
{ key: 'backup_s3_endpoint', value: null, description: 'S3 Endpoint' },
|
||||
{ key: 'backup_s3_region', value: 'auto', description: 'S3 Region' },
|
||||
{ key: 'backup_s3_user_agent', value: 'rclone/v1.68.0', description: 'S3 User-Agent' },
|
||||
{ key: 'backup_s3_bucket', value: null, description: 'S3 Bucket' },
|
||||
{ key: 'backup_s3_prefix', value: 'aether/backups/', description: 'S3 备份前缀' },
|
||||
{ key: 'backup_s3_access_key_id', value: null, description: 'S3 Access Key ID' },
|
||||
|
||||
@@ -275,6 +275,21 @@
|
||||
@update:model-value="backup.config.value.region = String($event)"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label
|
||||
for="backup-user-agent"
|
||||
class="block text-sm font-medium"
|
||||
>
|
||||
User-Agent
|
||||
</Label>
|
||||
<Input
|
||||
id="backup-user-agent"
|
||||
:model-value="backup.config.value.userAgent"
|
||||
class="mt-1"
|
||||
placeholder="rclone/v1.68.0"
|
||||
@update:model-value="backup.config.value.userAgent = String($event)"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label
|
||||
for="backup-prefix"
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface S3BackupConfig {
|
||||
scope: S3BackupScope
|
||||
endpoint: string
|
||||
region: string
|
||||
userAgent: string
|
||||
bucket: string
|
||||
prefix: string
|
||||
accessKeyId: string
|
||||
@@ -34,6 +35,7 @@ const CONFIG_KEY_BY_FIELD: Record<ConfigField, string> = {
|
||||
scope: 'backup_s3_scope',
|
||||
endpoint: 'backup_s3_endpoint',
|
||||
region: 'backup_s3_region',
|
||||
userAgent: 'backup_s3_user_agent',
|
||||
bucket: 'backup_s3_bucket',
|
||||
prefix: 'backup_s3_prefix',
|
||||
accessKeyId: 'backup_s3_access_key_id',
|
||||
@@ -63,6 +65,7 @@ function defaultS3BackupConfig(): S3BackupConfig {
|
||||
scope: 'data',
|
||||
endpoint: '',
|
||||
region: 'auto',
|
||||
userAgent: 'rclone/v1.68.0',
|
||||
bucket: '',
|
||||
prefix: 'aether/backups/',
|
||||
accessKeyId: '',
|
||||
@@ -192,6 +195,7 @@ export function useS3BackupConfig() {
|
||||
{ key: 'backup_s3_scope', value: config.value.scope, description: 'S3 备份范围' },
|
||||
{ key: 'backup_s3_endpoint', value: config.value.endpoint.trim() || null, description: 'S3 Endpoint' },
|
||||
{ key: 'backup_s3_region', value: config.value.region.trim() || 'auto', description: 'S3 Region' },
|
||||
{ key: 'backup_s3_user_agent', value: config.value.userAgent.trim() || 'rclone/v1.68.0', description: 'S3 User-Agent' },
|
||||
{ key: 'backup_s3_bucket', value: config.value.bucket.trim() || null, description: 'S3 Bucket' },
|
||||
{ key: 'backup_s3_prefix', value: config.value.prefix.trim() || 'aether/backups/', description: 'S3 备份前缀' },
|
||||
{ key: 'backup_s3_access_key_id', value: config.value.accessKeyId.trim() || null, description: 'S3 Access Key ID' },
|
||||
|
||||
Reference in New Issue
Block a user