Skip to main content

studio_worker/
lib.rs

1//! Library surface for the `studio-worker` binary.
2//!
3//! Exposes the worker's modules so integration tests (and downstream
4//! tooling) can drive the contract without going through the CLI.
5
6pub mod auto_register;
7pub mod autostart;
8pub mod cli;
9pub mod config;
10pub mod engine;
11pub mod http;
12pub mod runtime;
13pub mod service;
14pub mod sys;
15pub mod telemetry;
16#[doc(hidden)]
17pub mod test_support;
18pub mod types;
19#[cfg(feature = "ui")]
20pub mod ui;
21pub mod update;
22pub mod ws;
23
24pub const AGENT_VERSION: &str = env!("CARGO_PKG_VERSION");
25
26/// Sentry release identifier in the `<pkg>@<version>` form expected by
27/// Sentry's organisation-wide *Releases* feature.  Bare version strings
28/// collide across projects in the same org, so we namespace with the
29/// crate name.  Matches what `sentry::release_name!()` would expand to.
30pub const RELEASE_NAME: &str = concat!(env!("CARGO_PKG_NAME"), "@", env!("CARGO_PKG_VERSION"));
31
32/// Tracing target for CLI lifecycle events.  Stable so operators can
33/// filter with `RUST_LOG=studio_worker::cli=info`.
34const CLI_TRACE_TARGET: &str = "studio_worker::cli";
35
36/// Emit a single startup breadcrumb naming the agent version and the
37/// subcommand about to run.  With no `SENTRY_DSN` set (the default),
38/// nothing else anchors "which version started, running what" in
39/// `journalctl` — which matters most right after an auto-update
40/// re-execs the binary or a service manager restarts the unit.
41fn log_cli_startup(command: &cli::Command) {
42    tracing::info!(
43        target: CLI_TRACE_TARGET,
44        op = "startup",
45        version = AGENT_VERSION,
46        command = command.name(),
47        "studio-worker starting"
48    );
49}
50
51/// Dispatch table for the CLI subcommands.  Lives in the library so we
52/// can drive it from tests without invoking the binary.
53pub async fn run_cli(args: cli::Cli) -> anyhow::Result<()> {
54    log_cli_startup(&args.command);
55    match args.command {
56        cli::Command::Run => runtime::run(args.config.as_deref()).await,
57        cli::Command::Register {
58            api_base_url,
59            reset,
60        } => {
61            runtime::register(
62                args.config.as_deref(),
63                runtime::RegisterArgs {
64                    api_base_url,
65                    reset,
66                },
67            )
68            .await
69        }
70        cli::Command::Status => runtime::status(args.config.as_deref()).await,
71        cli::Command::InstallService => service::install(args.config.as_deref()),
72        cli::Command::UninstallService => service::uninstall(),
73        cli::Command::SetThreshold { gb } => runtime::set_threshold(args.config.as_deref(), gb),
74        cli::Command::Config => runtime::show_config(args.config.as_deref()),
75        cli::Command::CheckUpdate => runtime::check_update(args.config.as_deref()).await,
76        cli::Command::Ui => run_ui(args.config.as_deref()).await,
77    }
78}
79
80#[cfg(feature = "ui")]
81async fn run_ui(config_path: Option<&str>) -> anyhow::Result<()> {
82    ui::run(config_path)
83}
84
85#[cfg(not(feature = "ui"))]
86async fn run_ui(_config_path: Option<&str>) -> anyhow::Result<()> {
87    anyhow::bail!(
88        "this build of studio-worker was compiled without the `ui` cargo feature \
89         (it is on by default \u{2014} you built with `--no-default-features`).\n\
90         Reinstall with `cargo install studio-worker` (UI is the default), or use \
91         the desktop installer from the releases page, to enable the native UI."
92    )
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use crate::test_support::capture;
99
100    #[test]
101    fn startup_breadcrumb_names_version_and_command() {
102        let logs = capture(|| log_cli_startup(&cli::Command::Run));
103        assert!(logs.contains("INFO"), "expected INFO event, got: {logs}");
104        assert!(
105            logs.contains("studio_worker::cli"),
106            "expected the cli target, got: {logs}"
107        );
108        assert!(
109            logs.contains("op=\"startup\""),
110            "expected op=startup, got: {logs}"
111        );
112        assert!(
113            logs.contains("command=\"run\""),
114            "expected command field, got: {logs}"
115        );
116        assert!(
117            logs.contains(AGENT_VERSION),
118            "expected agent version, got: {logs}"
119        );
120    }
121}