rush_sync_server/commands/create/
command.rs1use crate::commands::command::Command;
3use crate::core::prelude::*;
4use crate::server::types::{ServerContext, ServerInfo, ServerStatus};
5use crate::server::utils::validation::validate_server_name;
6use uuid::Uuid;
7
8#[derive(Debug, Default)]
9pub struct CreateCommand;
10
11impl CreateCommand {
12 pub fn new() -> Self {
13 Self
14 }
15}
16
17impl Command for CreateCommand {
18 fn name(&self) -> &'static str {
19 "create"
20 }
21 fn description(&self) -> &'static str {
22 "Create web server(s) - supports bulk creation"
23 }
24 fn matches(&self, command: &str) -> bool {
25 command.trim().to_lowercase().starts_with("create")
26 }
27
28 fn execute_sync(&self, args: &[&str]) -> Result<String> {
29 let config = get_config()?;
30 let ctx = crate::server::shared::get_shared_context();
31
32 match self.parse_creation_args(args) {
34 CreationMode::Single { name, port } => {
35 self.create_single_server(&config, ctx, name, port)
36 }
37 CreationMode::BulkAuto { count } => {
38 self.create_bulk_servers(&config, ctx, count, None, None)
39 }
40 CreationMode::BulkWithBase {
41 base_name,
42 base_port,
43 count,
44 } => self.create_bulk_servers(&config, ctx, count, Some(base_name), Some(base_port)),
45 CreationMode::Invalid(error) => Err(AppError::Validation(error)),
46 }
47 }
48
49 fn priority(&self) -> u8 {
50 65
51 }
52}
53
54#[derive(Debug)]
55enum CreationMode {
56 Single {
57 name: Option<String>,
58 port: Option<u16>,
59 },
60 BulkAuto {
61 count: u32,
62 },
63 BulkWithBase {
64 base_name: String,
65 base_port: u16,
66 count: u32,
67 },
68 Invalid(String),
69}
70
71impl CreateCommand {
72 fn parse_creation_args(&self, args: &[&str]) -> CreationMode {
74 match args.len() {
75 0 => CreationMode::Single {
76 name: None,
77 port: None,
78 },
79
80 1 => {
81 if let Ok(port) = args[0].parse::<u16>() {
83 if port >= 1000 {
84 CreationMode::Single {
86 name: None,
87 port: Some(port),
88 }
89 } else if port > 0 && port <= 100 {
90 CreationMode::BulkAuto { count: port as u32 }
92 } else {
93 CreationMode::Invalid("Invalid port or count".to_string())
94 }
95 } else {
96 CreationMode::Single {
98 name: Some(args[0].to_string()),
99 port: None,
100 }
101 }
102 }
103
104 2 => {
105 if let Ok(port) = args[1].parse::<u16>() {
107 CreationMode::Single {
108 name: Some(args[0].to_string()),
109 port: Some(port),
110 }
111 } else {
112 CreationMode::Invalid("Invalid port number".to_string())
113 }
114 }
115
116 3 => {
117 if let (Ok(port), Ok(count)) = (args[1].parse::<u16>(), args[2].parse::<u32>()) {
119 if count == 0 {
120 return CreationMode::Invalid("Count must be > 0".to_string());
121 }
122 if count > 100 {
123 return CreationMode::Invalid(
124 "Maximum 100 servers per bulk operation".to_string(),
125 );
126 }
127 CreationMode::BulkWithBase {
128 base_name: args[0].to_string(),
129 base_port: port,
130 count,
131 }
132 } else {
133 CreationMode::Invalid("Invalid port or count".to_string())
134 }
135 }
136
137 _ => CreationMode::Invalid(
138 "Too many arguments. Usage: create [name] [port] [count]".to_string(),
139 ),
140 }
141 }
142
143 fn create_single_server(
145 &self,
146 config: &Config,
147 ctx: &ServerContext,
148 custom_name: Option<String>,
149 custom_port: Option<u16>,
150 ) -> Result<String> {
151 let result = self.create_server_internal(config, ctx, custom_name, custom_port)?;
152 Ok(format!("Server created: {}", result.summary))
153 }
154
155 fn create_bulk_servers(
157 &self,
158 config: &Config,
159 ctx: &ServerContext,
160 count: u32,
161 base_name: Option<String>,
162 base_port: Option<u16>,
163 ) -> Result<String> {
164 let initial_server_count = read_lock(&ctx.servers, "servers")?.len();
165
166 if initial_server_count + (count as usize) > config.server.max_concurrent {
168 return Err(AppError::Validation(format!(
169 "Bulk creation would exceed server limit: {} + {} > {} (max_concurrent)",
170 initial_server_count, count, config.server.max_concurrent
171 )));
172 }
173
174 let mut created_servers = Vec::new();
175 let mut failed_servers = Vec::new();
176
177 for i in 0..count {
178 let (name, port) =
179 if let (Some(ref base_name), Some(base_port)) = (&base_name, base_port) {
180 let name = format!("{}-{:03}", base_name, i + 1);
182 let port = base_port + (i as u16);
183 (Some(name), Some(port))
184 } else {
185 (None, None)
187 };
188
189 match self.create_server_internal(config, ctx, name, port) {
190 Ok(result) => {
191 created_servers.push(result);
192 }
193 Err(e) => {
194 failed_servers.push(format!("Server {}: {}", i + 1, e));
195
196 if !e.to_string().contains("bereits") && !e.to_string().contains("occupied") {
198 break;
199 }
200 }
201 }
202 }
203
204 let mut result = format!(
206 "Bulk creation completed: {} of {} servers created",
207 created_servers.len(),
208 count
209 );
210
211 if !created_servers.is_empty() {
212 result.push_str("\n\nCreated servers:");
213 for server in &created_servers {
214 result.push_str(&format!("\n {} - {}", server.name, server.summary));
215 }
216 }
217
218 if !failed_servers.is_empty() {
219 result.push_str("\n\nFailed:");
220 for failure in &failed_servers {
221 result.push_str(&format!("\n {}", failure));
222 }
223 }
224
225 let final_count = read_lock(&ctx.servers, "servers")?.len();
226 result.push_str(&format!(
227 "\n\nTotal servers: {}/{}",
228 final_count, config.server.max_concurrent
229 ));
230
231 Ok(result)
232 }
233
234 fn create_server_internal(
236 &self,
237 config: &Config,
238 ctx: &ServerContext,
239 custom_name: Option<String>,
240 custom_port: Option<u16>,
241 ) -> Result<ServerCreationResult> {
242 let id = Uuid::new_v4().to_string();
243
244 let name = if let Some(custom_name) = custom_name {
245 validate_server_name(&custom_name)?;
246 let servers = read_lock(&ctx.servers, "servers")?;
247 if servers.values().any(|s| s.name == custom_name) {
248 return Err(AppError::Validation(get_translation(
249 "server.error.name_taken",
250 &[&custom_name],
251 )));
252 }
253 custom_name
254 } else {
255 let server_number = self.find_next_server_number(ctx);
256 format!("rss-{:03}", server_number)
257 };
258
259 let port = if let Some(custom_port) = custom_port {
260 let min_port = config.server.port_range_start.max(1024);
261 if custom_port < min_port {
262 return Err(AppError::Validation(format!(
263 "Port must be >= {} (configured minimum: {})",
264 min_port, config.server.port_range_start
265 )));
266 }
267
268 if custom_port > config.server.port_range_end {
269 return Err(AppError::Validation(format!(
270 "Port {} exceeds configured maximum: {}",
271 custom_port, config.server.port_range_end
272 )));
273 }
274
275 let servers = read_lock(&ctx.servers, "servers")?;
276 if servers.values().any(|s| s.port == custom_port) {
277 return Err(AppError::Validation(get_translation(
278 "server.error.port_used",
279 &[&custom_port.to_string()],
280 )));
281 }
282 if !crate::server::utils::port::is_port_available(
283 custom_port,
284 &config.server.bind_address,
285 ) {
286 return Err(AppError::Validation(get_translation(
287 "server.error.port_occupied",
288 &[&custom_port.to_string()],
289 )));
290 }
291
292 custom_port
293 } else {
294 self.find_next_available_port(config)?
295 };
296
297 let timestamp = std::time::SystemTime::now()
298 .duration_since(std::time::UNIX_EPOCH)
299 .unwrap_or_default()
300 .as_secs();
301
302 let server_info = ServerInfo {
303 id: id.clone(),
304 name: name.clone(),
305 port,
306 status: ServerStatus::Stopped,
307 created_at: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
308 created_timestamp: timestamp,
309 };
310
311 if let Err(e) = crate::server::handlers::web::create_server_directory_and_files(&name, port)
313 {
314 return Err(AppError::Validation(format!(
315 "Failed to create server directory: {}",
316 e
317 )));
318 }
319
320 write_lock(&ctx.servers, "servers")?.insert(id.clone(), server_info.clone());
322
323 let registry = crate::server::shared::get_persistent_registry();
325 let server_info_clone = server_info.clone();
326 tokio::spawn(async move {
327 if let Ok(_persistent_servers) = registry.load_servers().await {
328 if let Err(e) = registry.add_server(server_info_clone).await {
329 log::error!("Failed to persist server: {}", e);
330 }
331 }
332 });
333
334 let summary = format!(
335 "'{}' (ID: {}) on port {} [PERSISTENT]",
336 name,
337 &id[0..8],
338 port
339 );
340
341 Ok(ServerCreationResult { name, summary })
342 }
343
344 fn find_next_available_port(&self, config: &Config) -> Result<u16> {
346 let ctx = crate::server::shared::get_shared_context();
347 let used_ports: std::collections::HashSet<u16> = {
348 let servers = ctx
349 .servers
350 .read()
351 .map_err(|_| AppError::Validation("Server-Context lock poisoned".to_string()))?;
352 servers.values().map(|s| s.port).collect()
353 };
354
355 let start_port = config.server.port_range_start;
356 let end_port = config.server.port_range_end;
357
358 if start_port >= end_port {
359 return Err(AppError::Validation(format!(
360 "Invalid port range: {} >= {}. Check config.",
361 start_port, end_port
362 )));
363 }
364
365 let max_attempts = ((end_port - start_port + 1) as usize).min(1000);
366
367 for i in 0..max_attempts {
368 let candidate_port = start_port + (i as u16);
369
370 if candidate_port > end_port {
371 break;
372 }
373
374 if !used_ports.contains(&candidate_port)
375 && crate::server::utils::port::is_port_available(
376 candidate_port,
377 &config.server.bind_address,
378 )
379 {
380 return Ok(candidate_port);
381 }
382 }
383
384 Err(AppError::Validation(format!(
385 "No available ports in range {}-{} after {} attempts",
386 start_port, end_port, max_attempts
387 )))
388 }
389
390 fn find_next_server_number(&self, ctx: &ServerContext) -> u32 {
391 let servers = match ctx.servers.read() {
392 Ok(s) => s,
393 Err(e) => {
394 log::error!("servers lock poisoned: {}", e);
395 return 1;
396 }
397 };
398 let mut existing_numbers = Vec::new();
399
400 for server in servers.values() {
401 if let Some(number_str) = server.name.strip_prefix("rss-") {
402 if let Ok(number) = number_str.parse::<u32>() {
403 existing_numbers.push(number);
404 }
405 }
406 }
407
408 existing_numbers.sort();
409 let mut next_number = 1;
410 for &existing in &existing_numbers {
411 if existing == next_number {
412 next_number += 1;
413 } else {
414 break;
415 }
416 }
417 next_number
418 }
419}
420
421#[derive(Debug)]
422struct ServerCreationResult {
423 name: String,
424 summary: String,
425}