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 <= 50 {
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 > 50 {
123 return CreationMode::Invalid(
124 "Maximum 50 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 = ctx.servers.read().unwrap().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 = ctx.servers.read().unwrap().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 let has_custom_name = custom_name.is_some();
244
245 let name = if let Some(custom_name) = custom_name {
246 validate_server_name(&custom_name)?;
247 let servers = ctx.servers.read().unwrap();
248 if servers.values().any(|s| s.name == custom_name) {
249 return Err(AppError::Validation(format!(
250 "Server-Name '{}' bereits vergeben!",
251 custom_name
252 )));
253 }
254 custom_name
255 } else {
256 let server_number = self.find_next_server_number(ctx);
257 format!("rss-{:03}", server_number)
258 };
259
260 let port = if let Some(custom_port) = custom_port {
261 let min_port = config.server.port_range_start.max(1024);
262 if custom_port < min_port {
263 return Err(AppError::Validation(format!(
264 "Port must be >= {} (configured minimum: {})",
265 min_port, config.server.port_range_start
266 )));
267 }
268
269 if custom_port > config.server.port_range_end {
270 return Err(AppError::Validation(format!(
271 "Port {} exceeds configured maximum: {}",
272 custom_port, config.server.port_range_end
273 )));
274 }
275
276 let servers = ctx.servers.read().unwrap();
277 if servers.values().any(|s| s.port == custom_port) {
278 return Err(AppError::Validation(format!(
279 "Port {} bereits verwendet!",
280 custom_port
281 )));
282 }
283 if !crate::server::utils::port::is_port_available(custom_port) {
284 return Err(AppError::Validation(format!(
285 "Port {} bereits belegt!",
286 custom_port
287 )));
288 }
289
290 custom_port
291 } else {
292 self.find_next_available_port(config)?
293 };
294
295 let timestamp = std::time::SystemTime::now()
296 .duration_since(std::time::UNIX_EPOCH)
297 .unwrap_or_default()
298 .as_secs();
299
300 let server_info = ServerInfo {
301 id: id.clone(),
302 name: name.clone(),
303 port,
304 status: ServerStatus::Stopped,
305 created_at: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
306 created_timestamp: timestamp,
307 };
308
309 if let Err(e) = crate::server::handlers::web::create_server_directory_and_files(&name, port)
311 {
312 return Err(AppError::Validation(format!(
313 "Failed to create server directory: {}",
314 e
315 )));
316 }
317
318 ctx.servers
320 .write()
321 .unwrap()
322 .insert(id.clone(), server_info.clone());
323
324 let registry = crate::server::shared::get_persistent_registry();
326 let server_info_clone = server_info.clone();
327 tokio::spawn(async move {
328 if let Ok(_persistent_servers) = registry.load_servers().await {
329 if let Err(e) = registry.add_server(server_info_clone).await {
330 log::error!("Failed to persist server: {}", e);
331 }
332 }
333 });
334
335 let summary = if has_custom_name {
336 format!(
337 "'{}' (ID: {}) on port {} [PERSISTENT]",
338 name,
339 &id[0..8],
340 port
341 )
342 } else {
343 format!(
344 "'{}' (ID: {}) on port {} [PERSISTENT]",
345 name,
346 &id[0..8],
347 port
348 )
349 };
350
351 Ok(ServerCreationResult { name, summary })
352 }
353
354 fn find_next_available_port(&self, config: &Config) -> Result<u16> {
356 let ctx = crate::server::shared::get_shared_context();
357 let used_ports: std::collections::HashSet<u16> = {
358 let servers = ctx
359 .servers
360 .read()
361 .map_err(|_| AppError::Validation("Server-Context lock poisoned".to_string()))?;
362 servers.values().map(|s| s.port).collect()
363 };
364
365 let start_port = config.server.port_range_start;
366 let end_port = config.server.port_range_end;
367
368 if start_port >= end_port {
369 return Err(AppError::Validation(format!(
370 "Invalid port range: {} >= {}. Check config.",
371 start_port, end_port
372 )));
373 }
374
375 let max_attempts = ((end_port - start_port + 1) as usize).min(1000);
376
377 for i in 0..max_attempts {
378 let candidate_port = start_port + (i as u16);
379
380 if candidate_port > end_port {
381 break;
382 }
383
384 if !used_ports.contains(&candidate_port)
385 && crate::server::utils::port::is_port_available(candidate_port)
386 {
387 return Ok(candidate_port);
388 }
389 }
390
391 Err(AppError::Validation(format!(
392 "No available ports in range {}-{} after {} attempts",
393 start_port, end_port, max_attempts
394 )))
395 }
396
397 fn find_next_server_number(&self, ctx: &ServerContext) -> u32 {
398 let servers = ctx.servers.read().unwrap();
399 let mut existing_numbers = Vec::new();
400
401 for server in servers.values() {
402 if let Some(number_str) = server.name.strip_prefix("rss-") {
403 if let Ok(number) = number_str.parse::<u32>() {
404 existing_numbers.push(number);
405 }
406 }
407 }
408
409 existing_numbers.sort();
410 let mut next_number = 1;
411 for &existing in &existing_numbers {
412 if existing == next_number {
413 next_number += 1;
414 } else {
415 break;
416 }
417 }
418 next_number
419 }
420}
421
422#[derive(Debug)]
423struct ServerCreationResult {
424 name: String,
425 summary: String,
426}