feat(daemon): add support for daemon on windows (#2014)

* fix: gracefully exit on windows

* feat(daemon): tcp support for windows

* feat(daemon): add tcp port configuration

* fix: logging and fix compiler error

* docs: add build dependency to the readme

fix(docs): move a line up

* fix: missing field error

* docs: adds the daemon section to the default config

* fix: clippy and fmt

* feat: Update README.md

Co-authored-by: Ellie Huxtable <ellie@elliehuxtable.com>

* refactor: changes tcp port and other stuff as per request

* fix(config): update default tcp port in example config

* fix: complier error on unix

* refactor: make the cfg stuff look better

---------

Co-authored-by: Ellie Huxtable <ellie@elliehuxtable.com>
This commit is contained in:
YummyOreo 2024-05-12 21:35:34 -05:00 committed by GitHub
parent 0da534d524
commit ce67e52772
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 131 additions and 45 deletions

View File

@ -29,7 +29,7 @@
Atuin replaces your existing shell history with a SQLite database, and records Atuin replaces your existing shell history with a SQLite database, and records
additional context for your commands. Additionally, it provides optional and additional context for your commands. Additionally, it provides optional and
_fully encrypted_ synchronisation of your history between machines, via an Atuin _fully encrypted_ synchronisation of your history between machines, via an Atuin
server. server.
@ -90,7 +90,7 @@ I wanted to. And I **really** don't want to.
- fish - fish
- nushell - nushell
- xonsh - xonsh
## Community ## Community
### Forum ### Forum
@ -102,11 +102,11 @@ Atuin has a community forum, please ask here for help and support: https://forum
Atuin also has a community Discord, available [here](https://discord.gg/jR3tfchVvW) Atuin also has a community Discord, available [here](https://discord.gg/jR3tfchVvW)
# Quickstart # Quickstart
## With the default sync server ## With the default sync server
This will sign you up for the default sync server, hosted by me. Everything is end-to-end encrypted, so your secrets are safe! This will sign you up for the default sync server, hosted by me. Everything is end-to-end encrypted, so your secrets are safe!
Read more below for offline-only usage, or for hosting your own server. Read more below for offline-only usage, or for hosting your own server.
``` ```
@ -130,10 +130,10 @@ Then restart your shell!
> section below. > section below.
## Offline only (no sync) ## Offline only (no sync)
``` ```
bash <(curl https://raw.githubusercontent.com/atuinsh/atuin/main/install.sh) bash <(curl https://raw.githubusercontent.com/atuinsh/atuin/main/install.sh)
atuin import auto atuin import auto
``` ```
@ -171,25 +171,28 @@ toolchain, then you can run:
``` ```
cargo install atuin cargo install atuin
``` ```
And then follow [the shell setup](#shell-plugin) And then follow [the shell setup](#shell-plugin)
#### Daemon Build Dependencies
[Protoc](https://grpc.io/docs/protoc-installation/) is required to build the daemon, which is enabled by default. This is because we use protobufs for the message serialization
### Homebrew ### Homebrew
``` ```
brew install atuin brew install atuin
``` ```
And then follow [the shell setup](#shell-plugin) And then follow [the shell setup](#shell-plugin)
### MacPorts ### MacPorts
Atuin is also available in [MacPorts](https://ports.macports.org/port/atuin/) Atuin is also available in [MacPorts](https://ports.macports.org/port/atuin/)
``` ```
sudo port install atuin sudo port install atuin
``` ```
And then follow [the shell setup](#shell-plugin) And then follow [the shell setup](#shell-plugin)
### Cave ### Cave
@ -223,7 +226,7 @@ Atuin is available in the Arch Linux [[extra] repository](https://archlinux.org/
``` ```
pacman -S atuin pacman -S atuin
``` ```
And then follow [the shell setup](#shell-plugin) And then follow [the shell setup](#shell-plugin)
### Xbps ### Xbps
@ -243,7 +246,7 @@ Atuin is available in the Termux package repository:
``` ```
pkg install atuin pkg install atuin
``` ```
And then follow [the shell setup](#shell-plugin) And then follow [the shell setup](#shell-plugin)
### From source ### From source
@ -253,7 +256,7 @@ git clone https://github.com/atuinsh/atuin.git
cd atuin/crates/atuin cd atuin/crates/atuin
cargo install --path . cargo install --path .
``` ```
And then follow [the shell setup](#shell-plugin) And then follow [the shell setup](#shell-plugin)
## Shell plugin ## Shell plugin
@ -273,9 +276,9 @@ echo 'eval "$(atuin init zsh)"' >> ~/.zshrc
zinit load atuinsh/atuin zinit load atuinsh/atuin
``` ```
#### Antigen #### Antigen
```sh ```sh
antigen bundle atuinsh/atuin@main antigen bundle atuinsh/atuin@main
``` ```
@ -345,7 +348,7 @@ atuin init fish | source
``` ```
to your `is-interactive` block in your `~/.config/fish/config.fish` file to your `is-interactive` block in your `~/.config/fish/config.fish` file
### Nushell ### Nushell
Run in *Nushell*: Run in *Nushell*:
@ -371,7 +374,7 @@ to the end of your `~/.xonshrc`
# Security # Security
If you find any security issues, we'd appreciate it if you could alert ellie@atuin.sh If you find any security issues, we'd appreciate it if you could alert ellie@atuin.sh
# Contributors # Contributors

View File

@ -210,3 +210,18 @@ records = true
## auto: length of the selected command. ## auto: length of the selected command.
## static: length of the longest command stored in the history. ## static: length of the longest command stored in the history.
# strategy = "auto" # strategy = "auto"
[daemon]
## Enables using the daemon to sync. Requires the daemon to be running in the background. Start it with `atuin daemon`
# enabled = false
## How often the daemon should sync in seconds
# sync_frequency = 300
## The path to the unix socket used by the daemon (on unix systems)
## linux/mac: ~/.local/share/atuin/atuin.sock
## windows: Not Supported
# socket_path = "~/atuin.sock"
## The port that should be used for TCP on non unix systems
# tcp_port = 8889

View File

@ -353,6 +353,9 @@ pub struct Daemon {
/// The path to the unix socket used by the daemon /// The path to the unix socket used by the daemon
pub socket_path: String, pub socket_path: String,
/// The port that should be used for TCP on non unix systems
pub tcp_port: u64,
} }
impl Default for Preview { impl Default for Preview {
@ -369,6 +372,7 @@ impl Default for Daemon {
enabled: false, enabled: false,
sync_frequency: 300, sync_frequency: 300,
socket_path: "".to_string(), socket_path: "".to_string(),
tcp_port: 8889,
} }
} }
} }
@ -706,6 +710,7 @@ impl Settings {
.set_default("daemon.sync_frequency", 300)? .set_default("daemon.sync_frequency", 300)?
.set_default("daemon.enabled", false)? .set_default("daemon.enabled", false)?
.set_default("daemon.socket_path", socket_path.to_str())? .set_default("daemon.socket_path", socket_path.to_str())?
.set_default("daemon.tcp_port", 8889)?
.set_default( .set_default(
"prefers_reduced_motion", "prefers_reduced_motion",
std::env::var("NO_MOTION") std::env::var("NO_MOTION")

View File

@ -1,8 +1,12 @@
use eyre::{eyre, Result}; use eyre::{eyre, Result};
use tokio::net::UnixStream; #[cfg(windows)]
use tokio::net::TcpStream;
use tonic::transport::{Channel, Endpoint, Uri}; use tonic::transport::{Channel, Endpoint, Uri};
use tower::service_fn; use tower::service_fn;
#[cfg(unix)]
use tokio::net::UnixStream;
use atuin_client::history::History; use atuin_client::history::History;
use crate::history::{ use crate::history::{
@ -15,6 +19,7 @@ pub struct HistoryClient {
// Wrap the grpc client // Wrap the grpc client
impl HistoryClient { impl HistoryClient {
#[cfg(unix)]
pub async fn new(path: String) -> Result<Self> { pub async fn new(path: String) -> Result<Self> {
let channel = Endpoint::try_from("http://atuin_local_daemon:0")? let channel = Endpoint::try_from("http://atuin_local_daemon:0")?
.connect_with_connector(service_fn(move |_: Uri| { .connect_with_connector(service_fn(move |_: Uri| {
@ -30,6 +35,21 @@ impl HistoryClient {
Ok(HistoryClient { client }) Ok(HistoryClient { client })
} }
#[cfg(not(unix))]
pub async fn new(port: u64) -> Result<Self> {
let channel = Endpoint::try_from("http://atuin_local_daemon:0")?
.connect_with_connector(service_fn(move |_: Uri| {
let url = format!("127.0.0.1:{}", port);
TcpStream::connect(url)
}))
.await
.map_err(|_| eyre!("failed to connect to local atuin daemon. Is it running?"))?;
let client = HistoryServiceClient::new(channel);
Ok(HistoryClient { client })
}
pub async fn start_history(&mut self, h: History) -> Result<String> { pub async fn start_history(&mut self, h: History) -> Result<String> {
let req = StartHistoryRequest { let req = StartHistoryRequest {
command: h.command, command: h.command,

View File

@ -13,8 +13,6 @@ use atuin_client::database::{Database, Sqlite as HistoryDatabase};
use atuin_client::history::{History, HistoryId}; use atuin_client::history::{History, HistoryId};
use dashmap::DashMap; use dashmap::DashMap;
use eyre::Result; use eyre::Result;
use tokio::net::UnixListener;
use tokio_stream::wrappers::UnixListenerStream;
use tonic::{transport::Server, Request, Response, Status}; use tonic::{transport::Server, Request, Response, Status};
use crate::history::history_server::{History as HistorySvc, HistoryServer}; use crate::history::history_server::{History as HistorySvc, HistoryServer};
@ -134,6 +132,7 @@ impl HistorySvc for HistoryService {
} }
} }
#[cfg(unix)]
async fn shutdown_signal(socket: PathBuf) { async fn shutdown_signal(socket: PathBuf) {
let mut term = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) let mut term = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("failed to register sigterm handler"); .expect("failed to register sigterm handler");
@ -150,6 +149,52 @@ async fn shutdown_signal(socket: PathBuf) {
eprintln!("Shutting down..."); eprintln!("Shutting down...");
} }
#[cfg(windows)]
async fn shutdown_signal() {
tokio::signal::windows::ctrl_c()
.expect("failed to register signal handler")
.recv()
.await;
eprintln!("Shutting down...");
}
#[cfg(unix)]
async fn start_server(settings: Settings, history: HistoryService) -> Result<()> {
use tokio::net::UnixListener;
use tokio_stream::wrappers::UnixListenerStream;
let socket = settings.daemon.socket_path.clone();
let uds = UnixListener::bind(socket.clone())?;
let uds_stream = UnixListenerStream::new(uds);
tracing::info!("listening on unix socket {:?}", socket);
Server::builder()
.add_service(HistoryServer::new(history))
.serve_with_incoming_shutdown(uds_stream, shutdown_signal(socket.into()))
.await?;
Ok(())
}
#[cfg(not(unix))]
async fn start_server(settings: Settings, history: HistoryService) -> Result<()> {
use tokio::net::TcpListener;
use tokio_stream::wrappers::TcpListenerStream;
let port = settings.daemon.tcp_port;
let url = format!("127.0.0.1:{}", port);
let tcp = TcpListener::bind(url).await?;
let tcp_stream = TcpListenerStream::new(tcp);
tracing::info!("listening on tcp port {:?}", port);
Server::builder()
.add_service(HistoryServer::new(history))
.serve_with_incoming_shutdown(tcp_stream, shutdown_signal())
.await?;
Ok(())
}
// break the above down when we end up with multiple services // break the above down when we end up with multiple services
/// Listen on a unix socket /// Listen on a unix socket
@ -168,12 +213,6 @@ pub async fn listen(
let history = HistoryService::new(history_store.clone(), history_db.clone()); let history = HistoryService::new(history_store.clone(), history_db.clone());
let socket = settings.daemon.socket_path.clone();
let uds = UnixListener::bind(socket.clone())?;
let uds_stream = UnixListenerStream::new(uds);
tracing::info!("listening on unix socket {:?}", socket);
// start services // start services
tokio::spawn(sync::worker( tokio::spawn(sync::worker(
settings.clone(), settings.clone(),
@ -182,10 +221,5 @@ pub async fn listen(
history_db, history_db,
)); ));
Server::builder() start_server(settings, history).await
.add_service(HistoryServer::new(history))
.serve_with_incoming_shutdown(uds_stream, shutdown_signal(socket.into()))
.await?;
Ok(())
} }

View File

@ -315,11 +315,15 @@ impl Cmd {
} }
if settings.daemon.enabled { if settings.daemon.enabled {
let resp = let resp = atuin_daemon::client::HistoryClient::new(
atuin_daemon::client::HistoryClient::new(settings.daemon.socket_path.clone()) #[cfg(not(unix))]
.await? settings.daemon.tcp_port,
.start_history(h) #[cfg(unix)]
.await?; settings.daemon.socket_path.clone(),
)
.await?
.start_history(h)
.await?;
// print the ID // print the ID
// we use this as the key for calling end // we use this as the key for calling end
@ -350,10 +354,15 @@ impl Cmd {
// We will need to keep the old code around for a while. // We will need to keep the old code around for a while.
// At the very least, while this is opt-in // At the very least, while this is opt-in
if settings.daemon.enabled { if settings.daemon.enabled {
atuin_daemon::client::HistoryClient::new(settings.daemon.socket_path.clone()) let resp = atuin_daemon::client::HistoryClient::new(
.await? #[cfg(not(unix))]
.end_history(id.to_string(), duration.unwrap_or(0), exit) settings.daemon.tcp_port,
.await?; #[cfg(unix)]
settings.daemon.socket_path.clone(),
)
.await?
.end_history(id.to_string(), duration.unwrap_or(0), exit)
.await?;
return Ok(()); return Ok(());
} }