diff --git a/Cargo.lock b/Cargo.lock index e3cfd20..425696f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1397,6 +1397,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.4" @@ -1596,7 +1605,9 @@ dependencies = [ "memchr", "mio 0.7.13", "num_cpus", + "once_cell", "pin-project-lite", + "signal-hook-registry", "tokio-macros", "winapi 0.3.9", ] diff --git a/syndicate-server/Cargo.toml b/syndicate-server/Cargo.toml index 0983378..02fa6e9 100644 --- a/syndicate-server/Cargo.toml +++ b/syndicate-server/Cargo.toml @@ -26,7 +26,7 @@ structopt = "0.3" tungstenite = "0.13" tokio-tungstenite = "0.14" -tokio = { version = "1.10", features = ["io-std", "time"] } +tokio = { version = "1.10", features = ["io-std", "time", "process"] } tokio-util = "0.6" tracing = "0.1" diff --git a/syndicate-server/protocols/schema-bundle.bin b/syndicate-server/protocols/schema-bundle.bin index e63e986..9c07d58 100644 --- a/syndicate-server/protocols/schema-bundle.bin +++ b/syndicate-server/protocols/schema-bundle.bin @@ -1,4 +1,4 @@ -´³bundle·µ³externalServices„´³schema·³version‘³ definitions·³Service´³refµ„³ DaemonService„³ClearEnv´³orµµ±present´³dict·³clearEnv´³named³clearEnv´³atom³Boolean„„„„„µ±absent´³dict·„„„„„³DaemonId³any³EnvValue´³orµµ±set´³atom³String„„µ±remove´³lit€„„„„³ DaemonDir´³orµµ±present´³dict·³dir´³named³dir´³atom³String„„„„„µ±absent´³dict·„„„„„³ DaemonEnv´³orµµ±present´³dict·³env´³named³env´³dictof´³refµ„³ EnvVariable„´³refµ„³EnvValue„„„„„„µ±absent´³dict·„„„„„³ -DaemonSpec´³andµ´³dict·³argv´³named³argv´³seqof´³atom³String„„„„„´³named³env´³refµ„³ DaemonEnv„„´³named³dir´³refµ„³ DaemonDir„„´³named³clearEnv´³refµ„³ClearEnv„„„„³ EnvVariable´³orµµ±string´³atom³String„„µ±symbol´³atom³Symbol„„„„³ DaemonProcess´³rec´³lit³daemon„´³tupleµ´³named³id´³refµ„³DaemonId„„´³named³config´³refµ„³ -DaemonSpec„„„„„³ DaemonService´³rec´³lit³daemon„´³tupleµ´³named³id´³refµ„³DaemonId„„„„„³ServiceDependency´³rec´³lit³ +´³bundle·µ³externalServices„´³schema·³version‘³ definitions·³Service´³refµ„³ DaemonService„³ClearEnv´³orµµ±present´³dict·³clearEnv´³named³clearEnv´³atom³Boolean„„„„„µ±invalid´³dict·³clearEnv´³named³clearEnv³any„„„„µ±absent´³dict·„„„„„³DaemonId³any³EnvValue´³orµµ±set´³atom³String„„µ±remove´³lit€„„µ±invalid³any„„„³ DaemonDir´³orµµ±present´³dict·³dir´³named³dir´³atom³String„„„„„µ±invalid´³dict·³dir´³named³dir³any„„„„µ±absent´³dict·„„„„„³ DaemonEnv´³orµµ±present´³dict·³env´³named³env´³dictof´³refµ„³ EnvVariable„´³refµ„³EnvValue„„„„„„µ±invalid´³dict·³env´³named³env³any„„„„µ±absent´³dict·„„„„„³ +DaemonSpec´³orµµ±simple´³refµ„³ CommandLine„„µ±full´³refµ„³FullDaemonSpec„„„„³ CommandLine´³orµµ±shell´³atom³String„„µ±full´³refµ„³FullCommandLine„„„„³ EnvVariable´³orµµ±string´³atom³String„„µ±symbol´³atom³Symbol„„µ±invalid³any„„„³ DaemonProcess´³rec´³lit³daemon„´³tupleµ´³named³id´³refµ„³DaemonId„„´³named³config´³refµ„³ +DaemonSpec„„„„„³ DaemonService´³rec´³lit³daemon„´³tupleµ´³named³id´³refµ„³DaemonId„„„„„³FullDaemonSpec´³andµ´³dict·³argv´³named³argv´³refµ„³ CommandLine„„„„´³named³env´³refµ„³ DaemonEnv„„´³named³dir´³refµ„³ DaemonDir„„´³named³clearEnv´³refµ„³ClearEnv„„„„³FullCommandLine´³ tuplePrefixµ´³named³program´³atom³String„„„´³named³args´³seqof´³atom³String„„„„³ServiceDependency´³rec´³lit³ depends-on„´³tupleµ´³named³depender³any„´³named³dependee³any„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³internalServices„´³schema·³version‘³ definitions·³ Milestone´³rec´³lit³ milestone„´³tupleµ´³named³name³any„„„„³ DebtReporter´³lit³ debt-reporter„³ ConfigWatcher´³rec´³lit³config-watcher„´³tupleµ´³named³path´³atom³String„„„„„³TcpRelayListener´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Tcp„„„„„³UnixRelayListener´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Unix„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„„„ \ No newline at end of file diff --git a/syndicate-server/protocols/schemas/externalServices.prs b/syndicate-server/protocols/schemas/externalServices.prs index a209189..5ff942e 100644 --- a/syndicate-server/protocols/schemas/externalServices.prs +++ b/syndicate-server/protocols/schemas/externalServices.prs @@ -9,10 +9,14 @@ ServiceDependency = . DaemonProcess = . DaemonId = any . -DaemonSpec = { argv: [string ...] } & @env DaemonEnv & @dir DaemonDir & @clearEnv ClearEnv . -DaemonEnv = @present { env: { EnvVariable: EnvValue ...:... } } / @absent {} . -DaemonDir = @present { dir: string } / @absent {} . -ClearEnv = @present { clearEnv: bool } / @absent {} . +DaemonSpec = @simple CommandLine / @full FullDaemonSpec . +FullDaemonSpec = { argv: CommandLine } & @env DaemonEnv & @dir DaemonDir & @clearEnv ClearEnv . +DaemonEnv = @present { env: { EnvVariable: EnvValue ...:... } } / @invalid { env: any } / @absent {} . +DaemonDir = @present { dir: string } / @invalid { dir: any } / @absent {} . +ClearEnv = @present { clearEnv: bool } / @invalid { clearEnv: any } / @absent {} . -EnvVariable = @string string / @symbol symbol . -EnvValue = @set string / @remove #f . +CommandLine = @shell string / @full FullCommandLine . +FullCommandLine = [@program string, @args string ...] . + +EnvVariable = @string string / @symbol symbol / @invalid any . +EnvValue = @set string / @remove #f / @invalid any . diff --git a/syndicate-server/src/services/daemon.rs b/syndicate-server/src/services/daemon.rs index e26d0d0..2f959bb 100644 --- a/syndicate-server/src/services/daemon.rs +++ b/syndicate-server/src/services/daemon.rs @@ -5,8 +5,10 @@ use std::sync::Arc; use syndicate::actor::*; use syndicate::supervise::{Supervisor, SupervisorConfiguration}; +use tokio::process; + use crate::language::language; -use crate::schemas::external_services::{DaemonService, DaemonSpec}; +use crate::schemas::external_services::*; use syndicate_macros::during; @@ -24,6 +26,36 @@ pub fn on_demand(t: &mut Activation, config_ds: Arc, root_ds: Arc) { }); } +fn cannot_start() -> ActorResult { + Err("Cannot start daemon process")? +} + +impl DaemonSpec { + fn elaborate(self) -> FullDaemonSpec { + match self { + DaemonSpec::Simple(command_line) => FullDaemonSpec { + argv: *command_line, + env: DaemonEnv::Absent, + dir: DaemonDir::Absent, + clear_env: ClearEnv::Absent, + }, + DaemonSpec::Full(spec) => *spec, + } + } +} + +impl CommandLine { + fn elaborate(self) -> FullCommandLine { + match self { + CommandLine::Shell(s) => FullCommandLine { + program: "sh".to_owned(), + args: vec!["-c".to_owned(), s], + }, + CommandLine::Full(command_line) => *command_line, + } + } +} + fn run( t: &mut Activation, config_ds: Arc, @@ -35,11 +67,79 @@ fn run( config_ds.assert(t, &(), &syndicate_macros::template!("")); } - let id = service.id.0.clone(); + Ok(during!(t, config_ds, language(), , |t: &mut Activation| { + match language().parse::(&config) { + Ok(config) => { + let config = config.elaborate(); + t.linked_task(syndicate::name!("subprocess"), async move { + tracing::info!(?config); + let argv = config.argv.elaborate(); + let mut cmd = process::Command::new(argv.program); + cmd.args(argv.args); + match config.dir { + DaemonDir::Present { dir } => { cmd.current_dir(dir); () }, + DaemonDir::Absent => (), + DaemonDir::Invalid { dir } => { + tracing::error!(?dir, "Invalid working directory"); + return cannot_start(); + } + } + match config.clear_env { + ClearEnv::Present { clear_env: true } => { cmd.env_clear(); () }, + ClearEnv::Present { clear_env: false } => (), + ClearEnv::Absent => (), + ClearEnv::Invalid { clear_env } => { + tracing::error!(?clear_env, "Invalid clearEnv setting"); + return cannot_start(); + } + } + match config.env { + DaemonEnv::Present { env } => { + for (k, v) in env { + if let Some(env_variable) = match k { + EnvVariable::String(k) => Some(k), + EnvVariable::Symbol(k) => Some(k), + EnvVariable::Invalid(env_variable) => { + tracing::error!(?env_variable, + "Invalid environment variable name"); + return cannot_start(); + } + } { + match v { + EnvValue::Set(value) => { cmd.env(env_variable, value); () } + EnvValue::Remove => { cmd.env_remove(env_variable); () } + EnvValue::Invalid(value) => { + tracing::error!(?env_variable, ?value, + "Invalid environment variable value"); + return cannot_start(); + } + } + } + } + } + DaemonEnv::Absent => (), + DaemonEnv::Invalid { env } => { + tracing::error!(?env, "Invalid daemon environment"); + return cannot_start(); + } + } - Ok(during!(t, config_ds, language(), , |_t| { - if let Ok(config) = language().parse::(&config) { - tracing::info!("daemon {:?} {:?}", &service, &config); + cmd.stdin(std::process::Stdio::null()); + cmd.stdout(std::process::Stdio::inherit()); + cmd.stderr(std::process::Stdio::inherit()); + cmd.kill_on_drop(true); + + tracing::info!(?cmd); + let mut child = cmd.spawn()?; + tracing::info!(status = ?child.wait().await); + + Ok(()) + }); + } + Err(_) => { + tracing::error!(?config, "Invalid DaemonSpec"); + return cannot_start(); + } } Ok(()) }))