Skip to main content

static_web_server/settings/
cli.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// This file is part of Static Web Server.
3// See https://static-web-server.net/ for more information
4// Copyright (C) 2019-present Jose Quintana <joseluisq.net>
5
6//! The server CLI options.
7
8use 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/// General server configuration available in CLI and config file options.
21#[derive(Parser, Debug)]
22#[command(author, about, long_about)]
23pub struct General {
24    #[arg(long, short = 'a', default_value = "::", env = "SERVER_HOST")]
25    /// Host address (E.g 127.0.0.1 or ::1)
26    pub host: String,
27
28    #[arg(long, short = 'p', default_value = "80", env = "SERVER_PORT")]
29    /// Host port
30    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    /// Instead of binding to a TCP port, accept incoming connections to an already-bound TCP
51    /// socket listener on the specified file descriptor number (usually zero). Requires that the
52    /// parent process (e.g. inetd, launchd, or systemd) binds an address and port on behalf of
53    /// static-web-server, before arranging for the resulting file descriptor to be inherited by
54    /// static-web-server. Cannot be used in conjunction with the port and host arguments. The
55    /// included systemd unit file utilises this feature to increase security by allowing the
56    /// static-web-server to be sandboxed more completely.
57    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    )] // We use 2 as the threads multiplier in Wasm, 1 in Native
77    /// Number of worker threads multiplier that'll be multiplied by the number of system CPUs
78    /// using the formula: `worker threads = number of CPUs * n` where `n` is the value that changes here.
79    /// When multiplier value is 0 or 1 then one thread per core is used.
80    /// Number of worker threads result should be a number between 1 and 32,768 though it is advised to keep this value on the smaller side.
81    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    )] // We use 20 in Wasm, 512 in Native (default for tokio)
101    /// Maximum number of blocking threads
102    pub max_blocking_threads: usize,
103
104    #[arg(long, short = 'd', default_value = "./public", env = "SERVER_ROOT")]
105    /// Root directory path of static files.
106    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    /// Resolve the web root directory at request time rather than at startup,
118    /// allowing symlinked root directories to be swapped at runtime.
119    pub use_relative_root: bool,
120
121    #[arg(long, default_value = "./50x.html", env = "SERVER_ERROR_PAGE_50X")]
122    /// HTML file path for 50x errors. If the path is not specified or simply doesn't exist
123    /// then the server will use a generic HTML error message.
124    /// If a relative path is used then it will be resolved under the root directory.
125    pub page50x: PathBuf,
126
127    #[arg(long, default_value = "./404.html", env = "SERVER_ERROR_PAGE_404")]
128    /// HTML file path for 404 errors. If the path is not specified or simply doesn't exist
129    /// then the server will use a generic HTML error message.
130    /// If a relative path is used then it will be resolved under the root directory.
131    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    /// A HTML file path (not relative to the root) used for GET requests when the requested path doesn't exist. The fallback page is served with a 200 status code, useful when using client routers. If the path doesn't exist then the feature is not activated.
137    pub page_fallback: PathBuf,
138
139    #[arg(long, short = 'g', default_value = "error", env = "SERVER_LOG_LEVEL")]
140    /// Specify a logging level in lower case. Values: error, warn, info, debug or trace
141    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    /// Enable or disable ANSI escape codes for colors and other text formatting of the log output.
153    pub log_with_ansi: bool,
154
155    #[arg(
156        long,
157        short = 'c',
158        default_value = "",
159        env = "SERVER_CORS_ALLOW_ORIGINS"
160    )]
161    /// Specify an optional CORS list of allowed origin hosts separated by commas. Host ports or protocols aren't being checked. Use an asterisk (*) to allow any host.
162    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    /// Specify an optional CORS list of allowed headers separated by commas. Default "origin, content-type". It requires `--cors-allow-origins` to be used along with.
171    pub cors_allow_headers: String,
172
173    #[arg(
174        long,
175        default_value = "origin, content-type",
176        env = "SERVER_CORS_EXPOSE_HEADERS"
177    )]
178    /// Specify an optional CORS list of exposed headers separated by commas. Default "origin, content-type". It requires `--cors-expose-origins` to be used along with.
179    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    /// Enable HTTP/2 with TLS support.
194    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    /// Specify the file path to read the certificate.
200    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    /// Specify the file path to read the private key.
206    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    /// Redirect all requests with scheme "http" to "https" for the current server instance. It depends on "http2" to be enabled.
221    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    /// Canonical host name or IP of the HTTPS (HTTPS/2) server. It depends on "https_redirect" to be enabled.
232    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    /// HTTP host port where the redirect server will listen for requests to redirect them to HTTPS. It depends on "https_redirect" to be enabled.
243    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    /// List of host names or IPs allowed to redirect from. HTTP requests must contain the HTTP 'Host' header and match against this list. It depends on "https_redirect" to be enabled.
254    pub https_redirect_from_hosts: String,
255
256    #[arg(long, default_value = "index.html", env = "SERVER_INDEX_FILES")]
257    /// List of files that will be used as an index for requests ending with the slash character (‘/’).
258    /// Files are checked in the specified order.
259    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    /// Gzip, Deflate, Brotli or Zstd compression on demand determined by the Accept-Encoding header and applied to text-based web file types only.
289    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    /// Compression level to apply for Gzip, Deflate, Brotli or Zstd compression.
310    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    /// Look up the pre-compressed file variant (`.gz`, `.br` or `.zst`) on disk of a requested file and serves it directly if available.
322    /// The compression type is determined by the `Accept-Encoding` header.
323    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    /// Enable directory listing for all requests ending with the slash character (‘/’).
338    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    /// Specify a default code number to order directory listing entries per `Name`, `Last modified` or `Size` attributes (columns). Code numbers supported: 0 (Name asc), 1 (Name desc), 2 (Last modified asc), 3 (Last modified desc), 4 (Size asc), 5 (Size desc). Default 6 (unordered)
349    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    /// Specify a content format for directory listing entries. Formats supported: "html" or "json". Default "html".
362    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    /// Specify list of enabled format(s) for directory download. Format supported: `targz`. Default to empty list (disabled).
379    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    /// Enable security headers by default when HTTP/2 feature is activated.
392    /// Headers included: "Strict-Transport-Security: max-age=63072000; includeSubDomains; preload" (2 years max-age),
393    /// "X-Frame-Options: DENY" and "Content-Security-Policy: frame-ancestors 'self'".
394    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    /// Enable cache control headers for incoming requests based on a set of file types. The file type list can be found on `src/control_headers.rs` file.
413    pub cache_control_headers: bool,
414
415    #[cfg(feature = "basic-auth")]
416    /// It provides The "Basic" HTTP Authentication scheme using credentials as "user-id:password" pairs. Password must be encoded using the "BCrypt" password-hashing function.
417    #[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    /// Defines a grace period in seconds after a `SIGTERM` signal is caught which will delay the server before to shut it down gracefully. The maximum value is 255 seconds.
422    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    /// Server TOML configuration file path.
432    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    /// Log incoming requests information along with its remote address if available using the `info` log level.
444    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    /// Log the X-Real-IP header for remote IP information.
456    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    /// Log the X-Forwarded-For header for remote IP information
468    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    /// List of IPs to use X-Forwarded-For from. The default is to trust all
478    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    /// Check for a trailing slash in the requested directory URI and redirect permanently (308) to the same path with a trailing slash suffix if it is missing.
490    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    /// Ignore hidden files/directories (dotfiles), preventing them to be served and being included in auto HTML index pages (directory listing).
502    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    /// Prevent following files or directories if any path name component is a symbolic link.
514    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    /// Enable markdown content negotiation. When a client sends Accept: text/markdown, serve .md or .html.md files if available.
526    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    /// Set a default `charset=utf-8` parameter on limited set of `text` responses that don't already have one.
538    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    /// Add a /health endpoint that doesn't generate any log entry and returns a 200 status code.
550    /// This is especially useful with Kubernetes liveness and readiness probes.
551    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    /// Enable the /metrics endpoint that exposes Prometheus metrics for HTTP requests, connections, and latency.
564    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    /// Enable the server's maintenance mode functionality.
576    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    /// Provide a custom HTTP status code when entering into maintenance mode. Default 503.
586    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    /// Provide a custom maintenance mode HTML file. If not provided then a generic message will be displayed.
596    pub maintenance_mode_file: PathBuf,
597
598    //
599    // Windows specific arguments and commands
600    //
601    #[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    /// Tell the web server to run in a Windows Service context. Note that the `install` subcommand will enable this option automatically.
613    pub windows_service: bool,
614
615    // Subcommands
616    #[command(subcommand)]
617    /// Subcommands for additional maintenance tasks, like installing and uninstalling the SWS Windows Service and generation of completions and man pages
618    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    /// Print version info and exit.
628    pub version: bool,
629}
630
631#[derive(Debug, clap::Subcommand)]
632/// Subcommands for additional maintenance tasks, like installing and uninstalling the SWS Windows Service and generation of completions and man pages
633pub enum Commands {
634    /// Install a Windows Service for the web server.
635    #[cfg(windows)]
636    #[command(name = "install")]
637    Install {},
638
639    /// Uninstall the current Windows Service.
640    #[cfg(windows)]
641    #[command(name = "uninstall")]
642    Uninstall {},
643
644    /// Generate man pages and shell completions
645    #[command(name = "generate")]
646    Generate {
647        /// Generate shell completions
648        #[arg(long)]
649        completions: bool,
650        /// Generate man pages
651        #[arg(long)]
652        man_pages: bool,
653        /// Path to write generated artifacts to
654        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}