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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
|
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]]
|
[[package]]
|
||||||
name = "constant_time_eq"
|
name = "constant_time_eq"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -452,6 +472,7 @@ dependencies = [
|
||||||
"caps",
|
"caps",
|
||||||
"clap",
|
"clap",
|
||||||
"colored",
|
"colored",
|
||||||
|
"const_format",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"dialoguer",
|
"dialoguer",
|
||||||
"dirs 3.0.1",
|
"dirs 3.0.1",
|
||||||
|
@ -481,6 +502,7 @@ dependencies = [
|
||||||
"markdown",
|
"markdown",
|
||||||
"named_pipe",
|
"named_pipe",
|
||||||
"notify",
|
"notify",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
|
|
|
@ -69,4 +69,6 @@ libc = "0.2.98"
|
||||||
espanso-mac-utils = { path = "../espanso-mac-utils" }
|
espanso-mac-utils = { path = "../espanso-mac-utils" }
|
||||||
|
|
||||||
[target.'cfg(target_os="linux")'.dependencies]
|
[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 anyhow::Result;
|
||||||
use log::{info, warn};
|
use const_format::formatcp;
|
||||||
use std::process::Command;
|
use regex::Regex;
|
||||||
use std::{fs::create_dir_all, process::ExitStatus};
|
use std::path::PathBuf;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use std::{fs::create_dir_all};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
// const SERVICE_PLIST_CONTENT: &str = include_str!("../../res/macos/com.federicoterzi.espanso.plist");
|
use crate::{error_eprintln, info_println, warn_eprintln};
|
||||||
// const SERVICE_PLIST_FILE_NAME: &str = "com.federicoterzi.espanso.plist";
|
|
||||||
|
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<()> {
|
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)]
|
#[derive(Error, Debug)]
|
||||||
pub enum RegisterError {
|
pub enum RegisterError {
|
||||||
#[error("launchctl load failed")]
|
#[error("systemctl command failed `{0}`")]
|
||||||
LaunchCtlLoadFailed,
|
SystemdCallFailed(anyhow::Error),
|
||||||
|
|
||||||
|
#[error("systemctl enable failed")]
|
||||||
|
SystemdEnableFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unregister() -> Result<()> {
|
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)]
|
#[derive(Error, Debug)]
|
||||||
pub enum UnregisterError {
|
pub enum UnregisterError {
|
||||||
#[error("plist entry not found")]
|
#[error("service file not found")]
|
||||||
PlistNotFound,
|
ServiceFileNotFound,
|
||||||
|
|
||||||
|
#[error("failed to disable systemd service")]
|
||||||
|
SystemdDisableFailed,
|
||||||
|
|
||||||
|
#[error("systemctl command failed `{0}`")]
|
||||||
|
SystemdCallFailed(anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_registered() -> bool {
|
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<()> {
|
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)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -59,9 +235,33 @@ pub enum StartError {
|
||||||
#[error("not registered as a service")]
|
#[error("not registered as a service")]
|
||||||
NotRegistered,
|
NotRegistered,
|
||||||
|
|
||||||
#[error("launchctl failed to run")]
|
#[error("systemd not found")]
|
||||||
LaunchCtlFailure,
|
SystemdNotFound,
|
||||||
|
|
||||||
#[error("launchctl exited with non-zero code `{0}`")]
|
#[error("failed to start systemctl: `{0}`")]
|
||||||
LaunchCtlNonZeroExit(ExitStatus),
|
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