use anyhow::{Context, Result, bail};
use scv_client::{ControlError, Layout};
use scv_protocol::{DaemonCommand, ErrorCode};
use scv_server::config::ConfigOverrides;
use std::io::{self, IsTerminal};
use std::path::Path;
use super::args::Command;
use super::control;
pub(crate) fn refuse_nested_daemon_control(command: &Command) -> Result<()> {
let depth = scv_tools::delegation::current_depth();
let lifecycle = match command {
Command::Run { .. } => Some("run"),
Command::Start { .. } => Some("start"),
Command::Stop => Some("stop"),
Command::Restart {
when_idle: true, ..
} => None,
Command::Confirm { .. } => None,
Command::Restart { .. } => Some("restart"),
Command::RestartWatchdog { .. } => Some("restart-watchdog"),
Command::Update { .. } => Some("update"),
Command::Channels { .. } => Some("channels"),
_ => None,
};
if depth > 0
&& let Some(action) = lifecycle
{
bail!(
"`scv {action}` is refused inside a delegated agent run (delegation depth {depth}); \
the host owner manages the daemon"
);
}
Ok(())
}
const RESTART_UNSUPPORTED: i32 = 3;
pub(crate) async fn restart_when_idle(
layout: &Layout,
version: Option<String>,
commit: Option<String>,
max_wait: Option<u64>,
) -> Result<()> {
let parent = std::env::var(scv_tools::delegation::PARENT_VARIABLE)
.ok()
.filter(|chain| !chain.trim().is_empty());
let status = match control(
layout,
DaemonCommand::RestartWhenIdle {
version,
commit,
parent,
max_wait_seconds: max_wait,
},
)
.await
{
Ok(status) => status,
Err(error) => {
if matches!(
error.downcast_ref::<ControlError>(),
Some(
ControlError::Unavailable(_)
| ControlError::Server {
code: ErrorCode::InvalidJson,
..
}
)
) {
eprintln!("{error:#}");
eprintln!(
"The daemon is not running or cannot schedule its own restart; restart its unit instead."
);
std::process::exit(RESTART_UNSUPPORTED);
}
return Err(error);
}
};
let info = status
.restart
.context("the daemon did not report the scheduled restart")?;
println!("Restart into v{} scheduled.", info.to_version);
println!("{}", describe_restart(&info));
Ok(())
}
pub(crate) fn describe_restart(info: &scv_protocol::RestartInfo) -> String {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |elapsed| elapsed.as_secs());
let left = info.deadline_unix_seconds.saturating_sub(now);
let waiting = match &info.waiting_for {
Some(what) => format!(
"waiting for {what}, restarting anyway in {}m{:02}s",
left / 60,
left % 60
),
None => "restarting now".into(),
};
let target = match &info.origin {
Some(origin) => format!("the chat on {origin} that asked"),
None => "the [notify] accounts".into(),
};
format!(
"Restart into v{}: {waiting}; the outcome goes to {target}.",
info.to_version
)
}
pub(crate) async fn run_daemon(
layout: &Layout,
workspace: &Path,
overrides: ConfigOverrides,
) -> Result<()> {
std::env::set_current_dir(workspace)
.with_context(|| format!("change to daemon workspace {}", workspace.display()))?;
scv_server::run_socket(layout, overrides).await
}
pub(crate) fn init_tracing() {
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn"));
let _ = tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(std::io::stderr)
.with_ansi(io::stderr().is_terminal())
.try_init();
}