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