spacetimedb_cli/subcommands/
start.rs

1use std::ffi::OsString;
2use std::io;
3use std::process::{Command, ExitCode};
4
5use anyhow::Context;
6use clap::{Arg, ArgMatches};
7use spacetimedb_paths::SpacetimePaths;
8
9use crate::util::resolve_sibling_binary;
10
11pub fn cli() -> clap::Command {
12    clap::Command::new("start")
13        .about("Start a local SpacetimeDB instance")
14        .long_about(
15            "\
16Start a local SpacetimeDB instance
17
18Run `spacetime start --help` to see all options.",
19        )
20        .disable_help_flag(true)
21        .arg(
22            Arg::new("edition")
23                .long("edition")
24                .help("The edition of SpacetimeDB to start up")
25                .value_parser(clap::value_parser!(Edition))
26                .default_value("standalone"),
27        )
28        .arg(
29            Arg::new("args")
30                .help("The args to pass to `spacetimedb-{edition} start`")
31                .value_parser(clap::value_parser!(OsString))
32                .allow_hyphen_values(true)
33                .num_args(0..),
34        )
35}
36
37#[derive(clap::ValueEnum, Clone, Copy)]
38enum Edition {
39    Standalone,
40    Cloud,
41}
42
43pub async fn exec(paths: &SpacetimePaths, args: &ArgMatches) -> anyhow::Result<ExitCode> {
44    let edition = args.get_one::<Edition>("edition").unwrap();
45    let args = args.get_many::<OsString>("args").unwrap_or_default();
46    let bin_name = match edition {
47        Edition::Standalone => "spacetimedb-standalone",
48        Edition::Cloud => "spacetimedb-cloud",
49    };
50    let bin_path = resolve_sibling_binary(bin_name)?;
51    let mut cmd = Command::new(&bin_path);
52    cmd.arg("start")
53        .arg("--data-dir")
54        .arg(&paths.data_dir)
55        .arg("--jwt-key-dir")
56        .arg(&paths.cli_config_dir)
57        .args(args);
58
59    exec_replace(&mut cmd).with_context(|| format!("exec failed for {}", bin_path.display()))
60}
61
62// implementation based on and docs taken verbatim from `cargo_util::ProcessBuilder::exec_replace`
63//
64/// Replaces the current process with the target process.
65///
66/// On Unix, this executes the process using the Unix syscall `execvp`, which will block
67/// this process, and will only return if there is an error.
68///
69/// On Windows this isn't technically possible. Instead we emulate it to the best of our
70/// ability. One aspect we fix here is that we specify a handler for the Ctrl-C handler.
71/// In doing so (and by effectively ignoring it) we should emulate proxying Ctrl-C
72/// handling to the application at hand, which will either terminate or handle it itself.
73/// According to Microsoft's documentation at
74/// <https://docs.microsoft.com/en-us/windows/console/ctrl-c-and-ctrl-break-signals>.
75/// the Ctrl-C signal is sent to all processes attached to a terminal, which should
76/// include our child process. If the child terminates then we'll reap them in Cargo
77/// pretty quickly, and if the child handles the signal then we won't terminate
78/// (and we shouldn't!) until the process itself later exits.
79pub(crate) fn exec_replace(cmd: &mut Command) -> io::Result<ExitCode> {
80    #[cfg(unix)]
81    {
82        use std::os::unix::process::CommandExt;
83        // if exec() succeeds, it diverges, so the function just returns an io::Error
84        let err = cmd.exec();
85        Err(err)
86    }
87    #[cfg(windows)]
88    {
89        use windows_sys::Win32::Foundation::{BOOL, FALSE, TRUE};
90        use windows_sys::Win32::System::Console::SetConsoleCtrlHandler;
91
92        unsafe extern "system" fn ctrlc_handler(_: u32) -> BOOL {
93            // Do nothing. Let the child process handle it.
94            TRUE
95        }
96        unsafe {
97            if SetConsoleCtrlHandler(Some(ctrlc_handler), TRUE) == FALSE {
98                return Err(io::Error::new(io::ErrorKind::Other, "Unable to set console handler"));
99            }
100        }
101
102        cmd.status()
103            .map(|status| ExitCode::from(status.code().unwrap_or(1).try_into().unwrap_or(1)))
104    }
105}