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
6// Enable the unstable `#[coverage(off)]` attribute used across the
7// crate (behind `#[cfg_attr(coverage_nightly, ...)]`) to exclude
8// host-, network-, and platform-dependent code from `cargo llvm-cov`.
9// `coverage_nightly` is set only by cargo-llvm-cov on a nightly
10// toolchain; on stable it's unset, so this gate (and every
11// `coverage(off)` annotation) compiles to nothing. Without it,
12// `cargo +nightly llvm-cov` fails to compile the crate (E0658).
13#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
14
15pub mod admission;
16pub mod auto_register;
17pub mod autostart;
18pub mod catalog;
19pub mod cli;
20pub mod config;
21pub mod control;
22pub mod daemon_api;
23pub mod daemon_client;
24pub mod daemon_link;
25pub mod daemon_lock;
26pub mod engine;
27pub mod host;
28pub mod http;
29pub mod job_gate;
30pub mod job_log;
31pub mod job_run;
32pub mod lifecycle;
33pub mod loaders;
34pub mod local;
35pub mod local_api;
36pub mod net;
37pub mod residency;
38pub mod runtime;
39pub mod secrets;
40pub mod service;
41pub mod stt_stream;
42pub mod sys;
43pub mod telemetry;
44#[doc(hidden)]
45pub mod test_support;
46pub mod thumbnail;
47pub mod types;
48#[cfg(feature = "ui")]
49pub mod ui;
50pub mod update;
51pub mod ws;
52
53pub const AGENT_VERSION: &str = env!("CARGO_PKG_VERSION");
54
55/// Sentry release identifier in the `<pkg>@<version>` form expected by
56/// Sentry's organisation-wide *Releases* feature.  Bare version strings
57/// collide across projects in the same org, so we namespace with the
58/// crate name.  Matches what `sentry::release_name!()` would expand to.
59pub const RELEASE_NAME: &str = concat!(env!("CARGO_PKG_NAME"), "@", env!("CARGO_PKG_VERSION"));
60
61/// Tracing target for CLI lifecycle events.  Stable so operators can
62/// filter with `RUST_LOG=studio_worker::cli=info`.
63const CLI_TRACE_TARGET: &str = "studio_worker::cli";
64
65/// Emit a single startup breadcrumb naming the agent version and the
66/// subcommand about to run.  With no `SENTRY_DSN` set (the default),
67/// nothing else anchors "which version started, running what" in
68/// `journalctl` — which matters most right after an auto-update
69/// re-execs the binary or a service manager restarts the unit.
70fn log_cli_startup(command: &cli::Command) {
71    tracing::info!(
72        target: CLI_TRACE_TARGET,
73        op = "startup",
74        version = AGENT_VERSION,
75        command = command.name(),
76        "studio-worker starting"
77    );
78}
79
80/// Dispatch table for the CLI subcommands.  Lives in the library so we
81/// can drive it from tests without invoking the binary.
82pub async fn run_cli(args: cli::Cli) -> anyhow::Result<()> {
83    log_cli_startup(&args.command);
84    match args.command {
85        cli::Command::Run { wait_for_lock } => {
86            runtime::run(args.config.as_deref(), wait_for_lock).await
87        }
88        cli::Command::Register {
89            api_base_url,
90            reset,
91        } => {
92            runtime::register(
93                args.config.as_deref(),
94                runtime::RegisterArgs {
95                    api_base_url,
96                    reset,
97                },
98            )
99            .await
100        }
101        cli::Command::Status => runtime::status(args.config.as_deref()).await,
102        cli::Command::Setup => service::setup(args.config.as_deref()),
103        cli::Command::InstallService => service::install(args.config.as_deref()),
104        cli::Command::UninstallService => service::uninstall(),
105        cli::Command::SetThreshold { gb } => runtime::set_threshold(args.config.as_deref(), gb),
106        cli::Command::Config => runtime::show_config(args.config.as_deref()),
107        cli::Command::CheckUpdate => runtime::check_update(args.config.as_deref()).await,
108        cli::Command::Ui => run_ui(args.config.as_deref()).await,
109    }
110}
111
112#[cfg(feature = "ui")]
113async fn run_ui(config_path: Option<&str>) -> anyhow::Result<()> {
114    ui::run(config_path)
115}
116
117#[cfg(not(feature = "ui"))]
118async fn run_ui(_config_path: Option<&str>) -> anyhow::Result<()> {
119    anyhow::bail!(
120        "this build of studio-worker was compiled without the `ui` cargo feature \
121         (it is on by default \u{2014} you built with `--no-default-features`).\n\
122         Reinstall with `cargo install studio-worker` (UI is the default), or use \
123         the desktop installer from the releases page, to enable the native UI."
124    )
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use crate::test_support::capture;
131
132    #[test]
133    fn startup_breadcrumb_names_version_and_command() {
134        let logs = capture(|| {
135            log_cli_startup(&cli::Command::Run {
136                wait_for_lock: false,
137            })
138        });
139        assert!(logs.contains("INFO"), "expected INFO event, got: {logs}");
140        assert!(
141            logs.contains("studio_worker::cli"),
142            "expected the cli target, got: {logs}"
143        );
144        assert!(
145            logs.contains("op=\"startup\""),
146            "expected op=startup, got: {logs}"
147        );
148        assert!(
149            logs.contains("command=\"run\""),
150            "expected command field, got: {logs}"
151        );
152        assert!(
153            logs.contains(AGENT_VERSION),
154            "expected agent version, got: {logs}"
155        );
156    }
157}