Skip to main content

bhtune_server/
cli.rs

1//! `bhtune-server`'s CLI surface: a `--config` flag, one liveness probe, and service-management
2//! subcommands.
3//!
4//! Kept deliberately tiny -- unlike the `bhtune` CLI, this binary is still meant to be run mostly
5//! unconfigured (env vars / `bhtune.toml` cover everything else, see `crate::run`). `--config`
6//! lets `install` bake an explicit, stable config path
7//! into the service's registered launch command (see `crate::service::service_launch_arguments`),
8//! which matters because a Windows service normally runs under a different account than
9//! whoever ran `install` interactively, so it would otherwise resolve `%APPDATA%` to a
10//! different (system) profile and appear to have "lost" any config/database an operator set
11//! up while testing from their own terminal session -- see
12//! `docs/getting-started/installation.md`'s "Run as a background service" section.
13//!
14//! `ServerCommand` is parsed identically on every platform (so `--help` output and argument
15//! validation are covered by CI's Windows *and* Linux jobs alike), even though only Windows
16//! can actually act on it -- see `crate::service`'s module doc comment for how the two other
17//! platforms respond instead.
18
19use std::path::PathBuf;
20
21use clap::{Parser, Subcommand};
22
23/// A command accepted by the `bhtune-server` binary.
24#[derive(Subcommand, Debug, Clone, Copy, PartialEq, Eq)]
25pub enum ServerCommand {
26    /// Probe the local HTTP liveness endpoint without starting server services.
27    Healthcheck,
28    /// Register bhtune-server as a Windows service (does not start it).
29    Install,
30    /// Stop (if running) and remove the registered service.
31    Uninstall,
32    /// Start the registered service.
33    Start,
34    /// Request the running service to stop.
35    Stop,
36    /// Print the registered service's current state.
37    Status,
38}
39
40/// The `bhtune-server` binary's command line.
41#[derive(Parser, Debug)]
42#[command(
43    name = "bhtune-server",
44    version,
45    about = "BHTune's HTTP API and embedded web GUI"
46)]
47pub struct Cli {
48    #[command(subcommand)]
49    pub command: Option<ServerCommand>,
50
51    /// Path to a TOML config file (default: platform-specific, see `bhtune_runtime::config`).
52    /// Baked into the registered launch command by `install` -- see this module's doc
53    /// comment.
54    #[arg(long, global = true, value_name = "PATH")]
55    pub config: Option<PathBuf>,
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn no_arguments_parses_to_no_subcommand_and_no_config() {
64        let cli = Cli::parse_from(["bhtune-server"]);
65        assert_eq!(cli.command, None);
66        assert_eq!(cli.config, None);
67    }
68
69    #[test]
70    fn config_flag_parses_without_a_subcommand() {
71        let cli = Cli::parse_from(["bhtune-server", "--config", "/etc/bhtune/bhtune.toml"]);
72        assert_eq!(cli.command, None);
73        assert_eq!(cli.config, Some(PathBuf::from("/etc/bhtune/bhtune.toml")));
74    }
75
76    #[test]
77    fn every_server_subcommand_parses() {
78        for (arg, expected) in [
79            ("healthcheck", ServerCommand::Healthcheck),
80            ("install", ServerCommand::Install),
81            ("uninstall", ServerCommand::Uninstall),
82            ("start", ServerCommand::Start),
83            ("stop", ServerCommand::Stop),
84            ("status", ServerCommand::Status),
85        ] {
86            let cli = Cli::parse_from(["bhtune-server", arg]);
87            assert_eq!(cli.command, Some(expected));
88        }
89    }
90
91    #[test]
92    fn healthcheck_accepts_the_global_config_flag_after_the_subcommand() {
93        let cli = Cli::parse_from([
94            "bhtune-server",
95            "healthcheck",
96            "--config",
97            "/etc/bhtune/bhtune.toml",
98        ]);
99        assert_eq!(cli.command, Some(ServerCommand::Healthcheck));
100        assert_eq!(cli.config, Some(PathBuf::from("/etc/bhtune/bhtune.toml")));
101    }
102
103    #[test]
104    fn install_carries_a_config_flag_given_after_the_subcommand() {
105        // `--config` is `global = true`, so clap accepts it either before or after the
106        // subcommand -- `install` (the only caller that reads it, via
107        // `crate::service::service_launch_arguments`) needs the latter to work too.
108        let cli = Cli::parse_from([
109            "bhtune-server",
110            "install",
111            "--config",
112            "C:\\ProgramData\\bhtune\\bhtune.toml",
113        ]);
114        assert_eq!(cli.command, Some(ServerCommand::Install));
115        assert_eq!(
116            cli.config,
117            Some(PathBuf::from("C:\\ProgramData\\bhtune\\bhtune.toml"))
118        );
119    }
120}