1use clap::Parser;
9use hyper::StatusCode;
10use std::{net::IpAddr, path::PathBuf};
11
12#[cfg(feature = "directory-listing")]
13use crate::directory_listing::DirListFmt;
14
15#[cfg(feature = "directory-listing-download")]
16use crate::directory_listing_download::DirDownloadFmt;
17
18use crate::Result;
19
20#[derive(Parser, Debug)]
22#[command(author, about, long_about)]
23pub struct General {
24 #[arg(long, short = 'a', default_value = "::", env = "SERVER_HOST")]
25 pub host: String,
27
28 #[arg(long, short = 'p', default_value = "80", env = "SERVER_PORT")]
29 pub port: u16,
31
32 #[cfg_attr(
33 feature = "http2",
34 arg(
35 long,
36 short = 'f',
37 env = "SERVER_LISTEN_FD",
38 conflicts_with_all(&["host", "port", "https_redirect"])
39 )
40 )]
41 #[cfg_attr(
42 not(feature = "http2"),
43 arg(
44 long,
45 short = 'f',
46 env = "SERVER_LISTEN_FD",
47 conflicts_with_all(&["host", "port"])
48 )
49 )]
50 pub fd: Option<usize>,
58
59 #[cfg_attr(
60 not(target_family = "wasm"),
61 arg(
62 long,
63 short = 'n',
64 default_value = "1",
65 env = "SERVER_THREADS_MULTIPLIER"
66 )
67 )]
68 #[cfg_attr(
69 target_family = "wasm",
70 arg(
71 long,
72 short = 'n',
73 default_value = "2",
74 env = "SERVER_THREADS_MULTIPLIER"
75 )
76 )] pub threads_multiplier: usize,
82
83 #[cfg_attr(
84 not(target_family = "wasm"),
85 arg(
86 long,
87 short = 'b',
88 default_value = "512",
89 env = "SERVER_MAX_BLOCKING_THREADS"
90 )
91 )]
92 #[cfg_attr(
93 target_family = "wasm",
94 arg(
95 long,
96 short = 'b',
97 default_value = "20",
98 env = "SERVER_MAX_BLOCKING_THREADS"
99 )
100 )] pub max_blocking_threads: usize,
103
104 #[arg(long, short = 'd', default_value = "./public", env = "SERVER_ROOT")]
105 pub root: PathBuf,
107
108 #[arg(
109 long,
110 default_value = "false",
111 default_missing_value("true"),
112 num_args(0..=1),
113 require_equals(false),
114 action = clap::ArgAction::Set,
115 env = "SERVER_USE_RELATIVE_ROOT",
116 )]
117 pub use_relative_root: bool,
120
121 #[arg(long, default_value = "./50x.html", env = "SERVER_ERROR_PAGE_50X")]
122 pub page50x: PathBuf,
126
127 #[arg(long, default_value = "./404.html", env = "SERVER_ERROR_PAGE_404")]
128 pub page404: PathBuf,
132
133 #[cfg(feature = "fallback-page")]
134 #[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
135 #[arg(long, default_value = "", value_parser = value_parser_pathbuf, env = "SERVER_FALLBACK_PAGE")]
136 pub page_fallback: PathBuf,
138
139 #[arg(long, short = 'g', default_value = "error", env = "SERVER_LOG_LEVEL")]
140 pub log_level: String,
142
143 #[arg(
144 long,
145 default_value = "false",
146 default_missing_value("true"),
147 num_args(0..=1),
148 require_equals(false),
149 action = clap::ArgAction::Set,
150 env = "SERVER_LOG_WITH_ANSI",
151 )]
152 pub log_with_ansi: bool,
154
155 #[arg(
156 long,
157 short = 'c',
158 default_value = "",
159 env = "SERVER_CORS_ALLOW_ORIGINS"
160 )]
161 pub cors_allow_origins: String,
163
164 #[arg(
165 long,
166 short = 'j',
167 default_value = "origin, content-type, authorization",
168 env = "SERVER_CORS_ALLOW_HEADERS"
169 )]
170 pub cors_allow_headers: String,
172
173 #[arg(
174 long,
175 default_value = "origin, content-type",
176 env = "SERVER_CORS_EXPOSE_HEADERS"
177 )]
178 pub cors_expose_headers: String,
180
181 #[arg(
182 long,
183 short = 't',
184 default_value = "false",
185 default_missing_value("true"),
186 num_args(0..=1),
187 require_equals(false),
188 action = clap::ArgAction::Set,
189 env = "SERVER_HTTP2_TLS",
190 )]
191 #[cfg(feature = "http2")]
192 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
193 pub http2: bool,
195
196 #[arg(long, required_if_eq("http2", "true"), env = "SERVER_HTTP2_TLS_CERT")]
197 #[cfg(feature = "http2")]
198 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
199 pub http2_tls_cert: Option<PathBuf>,
201
202 #[arg(long, required_if_eq("http2", "true"), env = "SERVER_HTTP2_TLS_KEY")]
203 #[cfg(feature = "http2")]
204 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
205 pub http2_tls_key: Option<PathBuf>,
207
208 #[arg(
209 long,
210 default_value = "false",
211 default_missing_value("true"),
212 num_args(0..=1),
213 require_equals(false),
214 action = clap::ArgAction::Set,
215 requires_if("true", "http2"),
216 env = "SERVER_HTTPS_REDIRECT"
217 )]
218 #[cfg(feature = "http2")]
219 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
220 pub https_redirect: bool,
222
223 #[arg(
224 long,
225 requires_if("true", "https_redirect"),
226 default_value = "localhost",
227 env = "SERVER_HTTPS_REDIRECT_HOST"
228 )]
229 #[cfg(feature = "http2")]
230 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
231 pub https_redirect_host: String,
233
234 #[arg(
235 long,
236 requires_if("true", "https_redirect"),
237 default_value = "80",
238 env = "SERVER_HTTPS_REDIRECT_FROM_PORT"
239 )]
240 #[cfg(feature = "http2")]
241 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
242 pub https_redirect_from_port: u16,
244
245 #[arg(
246 long,
247 requires_if("true", "https_redirect"),
248 default_value = "localhost",
249 env = "SERVER_HTTPS_REDIRECT_FROM_HOSTS"
250 )]
251 #[cfg(feature = "http2")]
252 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
253 pub https_redirect_from_hosts: String,
255
256 #[arg(long, default_value = "index.html", env = "SERVER_INDEX_FILES")]
257 pub index_files: String,
260
261 #[cfg(any(
262 feature = "compression",
263 feature = "compression-gzip",
264 feature = "compression-brotli",
265 feature = "compression-zstd",
266 feature = "compression-deflate"
267 ))]
268 #[cfg_attr(
269 docsrs,
270 doc(cfg(any(
271 feature = "compression",
272 feature = "compression-gzip",
273 feature = "compression-brotli",
274 feature = "compression-zstd",
275 feature = "compression-deflate"
276 )))
277 )]
278 #[arg(
279 long,
280 short = 'x',
281 default_value = "true",
282 default_missing_value("true"),
283 num_args(0..=1),
284 require_equals(false),
285 action = clap::ArgAction::Set,
286 env = "SERVER_COMPRESSION",
287 )]
288 pub compression: bool,
290
291 #[cfg(any(
292 feature = "compression",
293 feature = "compression-gzip",
294 feature = "compression-brotli",
295 feature = "compression-zstd",
296 feature = "compression-deflate"
297 ))]
298 #[cfg_attr(
299 docsrs,
300 doc(cfg(any(
301 feature = "compression",
302 feature = "compression-gzip",
303 feature = "compression-brotli",
304 feature = "compression-zstd",
305 feature = "compression-deflate"
306 )))
307 )]
308 #[arg(long, default_value = "default", env = "SERVER_COMPRESSION_LEVEL")]
309 pub compression_level: super::CompressionLevel,
311
312 #[arg(
313 long,
314 default_value = "false",
315 default_missing_value("true"),
316 num_args(0..=1),
317 require_equals(false),
318 action = clap::ArgAction::Set,
319 env = "SERVER_COMPRESSION_STATIC",
320 )]
321 pub compression_static: bool,
324
325 #[cfg(feature = "directory-listing")]
326 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
327 #[arg(
328 long,
329 short = 'z',
330 default_value = "false",
331 default_missing_value("true"),
332 num_args(0..=1),
333 require_equals(false),
334 action = clap::ArgAction::Set,
335 env = "SERVER_DIRECTORY_LISTING",
336 )]
337 pub directory_listing: bool,
339
340 #[cfg(feature = "directory-listing")]
341 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
342 #[arg(
343 long,
344 requires_if("true", "directory_listing"),
345 default_value = "6",
346 env = "SERVER_DIRECTORY_LISTING_ORDER"
347 )]
348 pub directory_listing_order: u8,
350
351 #[cfg(feature = "directory-listing")]
352 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
353 #[arg(
354 long,
355 value_enum,
356 requires_if("true", "directory_listing"),
357 default_value = "html",
358 env = "SERVER_DIRECTORY_LISTING_FORMAT",
359 ignore_case(true)
360 )]
361 pub directory_listing_format: DirListFmt,
363
364 #[cfg(feature = "directory-listing-download")]
365 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing-download")))]
366 #[arg(
367 long,
368 value_delimiter(','),
369 value_enum,
370 requires_ifs([
371 ("targz", "directory_listing"),
372 ]),
373 require_equals(true),
374 action = clap::ArgAction::Set,
375 env = "SERVER_DIRECTORY_LISTING_DOWNLOAD",
376 ignore_case(true)
377 )]
378 pub directory_listing_download: Vec<DirDownloadFmt>,
380
381 #[arg(
382 long,
383 default_value = "false",
384 default_value_if("http2", "true", Some("true")),
385 default_missing_value("true"),
386 num_args(0..=1),
387 require_equals(false),
388 action = clap::ArgAction::Set,
389 env = "SERVER_SECURITY_HEADERS",
390 )]
391 pub security_headers: bool,
395
396 #[arg(
397 long,
398 short = 'e',
399 default_value = "true",
400 env = "SERVER_CACHE_CONTROL_HEADERS"
401 )]
402 #[arg(
403 long,
404 short = 'e',
405 default_value = "true",
406 default_missing_value("true"),
407 num_args(0..=1),
408 require_equals(false),
409 action = clap::ArgAction::Set,
410 env = "SERVER_CACHE_CONTROL_HEADERS",
411 )]
412 pub cache_control_headers: bool,
414
415 #[cfg(feature = "basic-auth")]
416 #[arg(long, default_value = "", env = "SERVER_BASIC_AUTH")]
418 pub basic_auth: String,
419
420 #[arg(long, short = 'q', default_value = "0", env = "SERVER_GRACE_PERIOD")]
421 pub grace_period: u8,
423
424 #[arg(
425 long,
426 short = 'w',
427 default_value = "./sws.toml",
428 value_parser = value_parser_pathbuf,
429 env = "SERVER_CONFIG_FILE"
430 )]
431 pub config_file: PathBuf,
433
434 #[arg(
435 long,
436 default_value = "false",
437 default_missing_value("true"),
438 num_args(0..=1),
439 require_equals(false),
440 action = clap::ArgAction::Set,
441 env = "SERVER_LOG_REMOTE_ADDRESS",
442 )]
443 pub log_remote_address: bool,
445
446 #[arg(
447 long,
448 default_value = "false",
449 default_missing_value("true"),
450 num_args(0..=1),
451 require_equals(false),
452 action = clap::ArgAction::Set,
453 env = "SERVER_LOG_X_REAL_IP",
454 )]
455 pub log_x_real_ip: bool,
457
458 #[arg(
459 long,
460 default_value = "false",
461 default_missing_value("true"),
462 num_args(0..=1),
463 require_equals(false),
464 action = clap::ArgAction::Set,
465 env = "SERVER_LOG_FORWARDED_FOR",
466 )]
467 pub log_forwarded_for: bool,
469
470 #[arg(
471 long,
472 require_equals(false),
473 value_delimiter(','),
474 action = clap::ArgAction::Set,
475 env = "SERVER_TRUSTED_PROXIES",
476 )]
477 pub trusted_proxies: Vec<IpAddr>,
479
480 #[arg(
481 long,
482 default_value = "true",
483 default_missing_value("true"),
484 num_args(0..=1),
485 require_equals(false),
486 action = clap::ArgAction::Set,
487 env = "SERVER_REDIRECT_TRAILING_SLASH",
488 )]
489 pub redirect_trailing_slash: bool,
491
492 #[arg(
493 long,
494 default_value = "true",
495 default_missing_value("true"),
496 num_args(0..=1),
497 require_equals(false),
498 action = clap::ArgAction::Set,
499 env = "SERVER_IGNORE_HIDDEN_FILES",
500 )]
501 pub ignore_hidden_files: bool,
503
504 #[arg(
505 long,
506 default_value = "true",
507 default_missing_value("true"),
508 num_args(0..=1),
509 require_equals(false),
510 action = clap::ArgAction::Set,
511 env = "SERVER_DISABLE_SYMLINKS",
512 )]
513 pub disable_symlinks: bool,
515
516 #[arg(
517 long,
518 default_value = "false",
519 default_missing_value("true"),
520 num_args(0..=1),
521 require_equals(false),
522 action = clap::ArgAction::Set,
523 env = "SERVER_ACCEPT_MARKDOWN",
524 )]
525 pub accept_markdown: bool,
527
528 #[arg(
529 long,
530 default_value = "true",
531 default_missing_value("true"),
532 num_args(0..=1),
533 require_equals(false),
534 action = clap::ArgAction::Set,
535 env = "SERVER_TEXT_CHARSET",
536 )]
537 pub text_charset: bool,
539
540 #[arg(
541 long,
542 default_value = "false",
543 default_missing_value("true"),
544 num_args(0..=1),
545 require_equals(false),
546 action = clap::ArgAction::Set,
547 env = "SERVER_HEALTH",
548 )]
549 pub health: bool,
552
553 #[cfg(feature = "metrics")]
554 #[arg(
555 long,
556 default_value = "false",
557 default_missing_value("true"),
558 num_args(0..=1),
559 require_equals(false),
560 action = clap::ArgAction::Set,
561 env = "SERVER_METRICS",
562 )]
563 pub metrics: bool,
565
566 #[arg(
567 long,
568 default_value = "false",
569 default_missing_value("true"),
570 num_args(0..=1),
571 require_equals(false),
572 action = clap::ArgAction::Set,
573 env = "SERVER_MAINTENANCE_MODE"
574 )]
575 pub maintenance_mode: bool,
577
578 #[arg(
579 long,
580 default_value = "503",
581 value_parser = value_parser_status_code,
582 requires_if("true", "maintenance_mode"),
583 env = "SERVER_MAINTENANCE_MODE_STATUS"
584 )]
585 pub maintenance_mode_status: StatusCode,
587
588 #[arg(
589 long,
590 default_value = "",
591 value_parser = value_parser_pathbuf,
592 requires_if("true", "maintenance_mode"),
593 env = "SERVER_MAINTENANCE_MODE_FILE"
594 )]
595 pub maintenance_mode_file: PathBuf,
597
598 #[cfg(windows)]
602 #[arg(
603 long,
604 short = 's',
605 default_value = "false",
606 default_missing_value("true"),
607 num_args(0..=1),
608 require_equals(false),
609 action = clap::ArgAction::Set,
610 env = "SERVER_WINDOWS_SERVICE",
611 )]
612 pub windows_service: bool,
614
615 #[command(subcommand)]
617 pub commands: Option<Commands>,
619
620 #[arg(
621 long,
622 short = 'V',
623 default_value = "false",
624 default_missing_value("true")
625 )]
626 #[doc(hidden)]
627 pub version: bool,
629}
630
631#[derive(Debug, clap::Subcommand)]
632pub enum Commands {
634 #[cfg(windows)]
636 #[command(name = "install")]
637 Install {},
638
639 #[cfg(windows)]
641 #[command(name = "uninstall")]
642 Uninstall {},
643
644 #[command(name = "generate")]
646 Generate {
647 #[arg(long)]
649 completions: bool,
650 #[arg(long)]
652 man_pages: bool,
653 out_dir: PathBuf,
655 },
656}
657
658fn value_parser_pathbuf(s: &str) -> Result<PathBuf, String> {
659 Ok(PathBuf::from(s))
660}
661
662fn value_parser_status_code(s: &str) -> Result<StatusCode, String> {
663 match s.parse::<u16>() {
664 Ok(code) => StatusCode::from_u16(code).map_err(|err| err.to_string()),
665 Err(err) => Err(err.to_string()),
666 }
667}