feat(core): implement linux service methods
This commit is contained in:
parent
40a307c38d
commit
fd2f385858
22
Cargo.lock
generated
22
Cargo.lock
generated
|
@ -221,6 +221,26 @@ version = "0.4.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ea7d6aeb2ebd1ee24f7b7e1b23242ef5a56b3a693733b99bfbe5ef31d0306"
|
||||
dependencies = [
|
||||
"const_format_proc_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format_proc_macros"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29c36c619c422113552db4eb28cddba8faa757e33f758cc3415bd2885977b591"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"unicode-xid 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
|
@ -452,6 +472,7 @@ dependencies = [
|
|||
"caps",
|
||||
"clap",
|
||||
"colored",
|
||||
"const_format",
|
||||
"crossbeam",
|
||||
"dialoguer",
|
||||
"dirs 3.0.1",
|
||||
|
@ -481,6 +502,7 @@ dependencies = [
|
|||
"markdown",
|
||||
"named_pipe",
|
||||
"notify",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
|
|
|
@ -69,4 +69,6 @@ libc = "0.2.98"
|
|||
espanso-mac-utils = { path = "../espanso-mac-utils" }
|
||||
|
||||
[target.'cfg(target_os="linux")'.dependencies]
|
||||
caps = "0.5.2"
|
||||
caps = "0.5.2"
|
||||
const_format = "0.2.14"
|
||||
regex = "1.4.3"
|
|
@ -18,40 +18,216 @@
|
|||
*/
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{info, warn};
|
||||
use std::process::Command;
|
||||
use std::{fs::create_dir_all, process::ExitStatus};
|
||||
use const_format::formatcp;
|
||||
use regex::Regex;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::{fs::create_dir_all};
|
||||
use thiserror::Error;
|
||||
|
||||
// const SERVICE_PLIST_CONTENT: &str = include_str!("../../res/macos/com.federicoterzi.espanso.plist");
|
||||
// const SERVICE_PLIST_FILE_NAME: &str = "com.federicoterzi.espanso.plist";
|
||||
use crate::{error_eprintln, info_println, warn_eprintln};
|
||||
|
||||
const LINUX_SERVICE_NAME: &str = "espanso";
|
||||
const LINUX_SERVICE_CONTENT: &str = include_str!("../../res/linux/systemd.service");
|
||||
const LINUX_SERVICE_FILENAME: &str = &formatcp!("{}.service", LINUX_SERVICE_NAME);
|
||||
|
||||
pub fn register() -> Result<()> {
|
||||
todo!();
|
||||
let service_file = get_service_file_path()?;
|
||||
|
||||
if service_file.exists() {
|
||||
warn_eprintln!("service file already exists, this operation will overwrite it");
|
||||
}
|
||||
|
||||
info_println!("creating service file in {:?}", service_file);
|
||||
let espanso_path = std::env::current_exe().expect("unable to get espanso executable path");
|
||||
|
||||
let service_content = String::from(LINUX_SERVICE_CONTENT).replace(
|
||||
"{{{espanso_path}}}",
|
||||
&espanso_path.to_string_lossy().to_string(),
|
||||
);
|
||||
|
||||
std::fs::write(service_file, service_content)?;
|
||||
|
||||
info_println!("enabling systemd service");
|
||||
|
||||
match Command::new("systemctl")
|
||||
.args(&["--user", "enable", LINUX_SERVICE_NAME])
|
||||
.status()
|
||||
{
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
return Err(RegisterError::SystemdEnableFailed.into());
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error_eprintln!("unable to call systemctl: {}", err);
|
||||
return Err(RegisterError::SystemdCallFailed(err.into()).into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RegisterError {
|
||||
#[error("launchctl load failed")]
|
||||
LaunchCtlLoadFailed,
|
||||
#[error("systemctl command failed `{0}`")]
|
||||
SystemdCallFailed(anyhow::Error),
|
||||
|
||||
#[error("systemctl enable failed")]
|
||||
SystemdEnableFailed,
|
||||
}
|
||||
|
||||
pub fn unregister() -> Result<()> {
|
||||
todo!();
|
||||
let service_file = get_service_file_path()?;
|
||||
|
||||
if !service_file.exists() {
|
||||
return Err(UnregisterError::ServiceFileNotFound.into());
|
||||
}
|
||||
|
||||
info_println!("disabling espanso systemd service");
|
||||
|
||||
match Command::new("systemctl")
|
||||
.args(&["--user", "disable", LINUX_SERVICE_NAME])
|
||||
.status()
|
||||
{
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
return Err(UnregisterError::SystemdDisableFailed.into());
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error_eprintln!("unable to call systemctl: {}", err);
|
||||
return Err(UnregisterError::SystemdCallFailed(err.into()).into());
|
||||
}
|
||||
}
|
||||
|
||||
info_println!("deleting espanso systemd entry");
|
||||
std::fs::remove_file(service_file)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum UnregisterError {
|
||||
#[error("plist entry not found")]
|
||||
PlistNotFound,
|
||||
#[error("service file not found")]
|
||||
ServiceFileNotFound,
|
||||
|
||||
#[error("failed to disable systemd service")]
|
||||
SystemdDisableFailed,
|
||||
|
||||
#[error("systemctl command failed `{0}`")]
|
||||
SystemdCallFailed(anyhow::Error),
|
||||
}
|
||||
|
||||
pub fn is_registered() -> bool {
|
||||
todo!();
|
||||
let res = Command::new("systemctl")
|
||||
.args(&["--user", "is-enabled", LINUX_SERVICE_NAME])
|
||||
.output();
|
||||
if let Ok(output) = res {
|
||||
if !output.status.success() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the systemd service points to the right binary
|
||||
lazy_static! {
|
||||
static ref EXEC_PATH_REGEX: Regex = Regex::new("ExecStart=(?P<path>.*?)\\s").unwrap();
|
||||
}
|
||||
|
||||
match Command::new("systemctl")
|
||||
.args(&["--user", "cat", LINUX_SERVICE_NAME])
|
||||
.output()
|
||||
{
|
||||
Ok(cmd_output) => {
|
||||
let output = String::from_utf8_lossy(cmd_output.stdout.as_slice());
|
||||
let output = output.trim();
|
||||
if cmd_output.status.success() {
|
||||
let caps = EXEC_PATH_REGEX.captures(output).unwrap();
|
||||
let path = caps.get(1).map_or("", |m| m.as_str());
|
||||
let espanso_path =
|
||||
std::env::current_exe().expect("unable to get espanso executable path");
|
||||
|
||||
if espanso_path.to_string_lossy() != path {
|
||||
error_eprintln!("Espanso is registered as a systemd service, but it points to another binary location:");
|
||||
error_eprintln!("");
|
||||
error_eprintln!(" {}", path);
|
||||
error_eprintln!("");
|
||||
error_eprintln!("This could have been caused by an update that changed its location.");
|
||||
error_eprintln!("To solve the problem, please unregister and register espanso again with these commands:");
|
||||
error_eprintln!("");
|
||||
error_eprintln!(" espanso service unregister && espanso service register");
|
||||
error_eprintln!("");
|
||||
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
error_eprintln!("systemctl command returned non-zero exit code");
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error_eprintln!("failed to execute systemctl: {}", err);
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_service() -> Result<()> {
|
||||
todo!();
|
||||
// Check if systemd is available in the system
|
||||
match Command::new("systemctl")
|
||||
.args(&["--version"])
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.status()
|
||||
{
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
return Err(StartError::SystemctlNonZeroExitCode.into());
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
error_eprintln!(
|
||||
"Systemd was not found in this system, which means espanso can't run in managed mode"
|
||||
);
|
||||
error_eprintln!("You can run it in unmanaged mode with `espanso service start --unmanaged`");
|
||||
error_eprintln!("");
|
||||
error_eprintln!("NOTE: unmanaged mode means espanso does not rely on the system service manager");
|
||||
error_eprintln!(" to run, but as a result, you are in charge of starting/stopping espanso");
|
||||
error_eprintln!(" when needed.");
|
||||
return Err(StartError::SystemdNotFound.into());
|
||||
}
|
||||
}
|
||||
|
||||
if !is_registered() {
|
||||
error_eprintln!("Unable to start espanso as a service as it's not been registered.");
|
||||
error_eprintln!("You can either register it first with `espanso service register` or");
|
||||
error_eprintln!("you can run it in unmanaged mode with `espanso service start --unmanaged`");
|
||||
error_eprintln!("");
|
||||
error_eprintln!("NOTE: unmanaged mode means espanso does not rely on the system service manager");
|
||||
error_eprintln!(" to run, but as a result, you are in charge of starting/stopping espanso");
|
||||
error_eprintln!(" when needed.");
|
||||
return Err(StartError::NotRegistered.into());
|
||||
}
|
||||
|
||||
match Command::new("systemctl")
|
||||
.args(&["--user", "start", LINUX_SERVICE_NAME])
|
||||
.status()
|
||||
{
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
return Err(StartError::SystemctlStartFailed.into());
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(StartError::SystemctlFailed(err.into()).into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -59,9 +235,33 @@ pub enum StartError {
|
|||
#[error("not registered as a service")]
|
||||
NotRegistered,
|
||||
|
||||
#[error("launchctl failed to run")]
|
||||
LaunchCtlFailure,
|
||||
#[error("systemd not found")]
|
||||
SystemdNotFound,
|
||||
|
||||
#[error("launchctl exited with non-zero code `{0}`")]
|
||||
LaunchCtlNonZeroExit(ExitStatus),
|
||||
#[error("failed to start systemctl: `{0}`")]
|
||||
SystemctlFailed(anyhow::Error),
|
||||
|
||||
#[error("systemctl non-zero exit code")]
|
||||
SystemctlNonZeroExitCode,
|
||||
|
||||
#[error("failed to launch espanso service through systemctl")]
|
||||
SystemctlStartFailed,
|
||||
}
|
||||
|
||||
fn get_service_file_dir() -> Result<PathBuf> {
|
||||
// User level systemd services should be placed in this directory:
|
||||
// $XDG_CONFIG_HOME/systemd/user/, usually: ~/.config/systemd/user/
|
||||
let config_dir = dirs::config_dir().expect("Could not get configuration directory");
|
||||
let systemd_dir = config_dir.join("systemd");
|
||||
let user_dir = systemd_dir.join("user");
|
||||
|
||||
if !user_dir.is_dir() {
|
||||
create_dir_all(&user_dir)?;
|
||||
}
|
||||
|
||||
Ok(user_dir)
|
||||
}
|
||||
|
||||
fn get_service_file_path() -> Result<PathBuf> {
|
||||
Ok(get_service_file_dir()?.join(LINUX_SERVICE_FILENAME))
|
||||
}
|
||||
|
|
11
espanso/src/res/linux/systemd.service
Normal file
11
espanso/src/res/linux/systemd.service
Normal file
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=espanso
|
||||
|
||||
[Service]
|
||||
ExecStart={{{espanso_path}}} launcher
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
Loading…
Reference in New Issue
Block a user