Repository: atuinsh/atuin (language: rust)

Issue:
[Bug]: atuin does not compile on windows since daemon was added
### What did you expect to happen?

When compiling atuin on Windows using `cargo build --no-default-features --features=client,sync,clipboard,check-update,server` the proccess completes and I get a fresh and shiny `atuin.exe`.

### What happened?

When compiling atuin on Windows using `cargo build --no-default-features --features=client,sync,clipboard,check-update,server` the build fails with errors:

```
$ cargo build --no-default-features --features=client,sync,clipboard,check-update,server
   Compiling atuin-client v18.2.0 (D:\workspaces\atuin\crates\atuin-client)
   Compiling atuin v18.2.0 (D:\workspaces\atuin\crates\atuin)
   Compiling atuin-history v0.1.0 (D:\workspaces\atuin\crates\atuin-history)
   Compiling atuin-dotfiles v0.2.0 (D:\workspaces\atuin\crates\atuin-dotfiles)
   Compiling atuin-daemon v0.1.0 (D:\workspaces\atuin\crates\atuin-daemon)
error[E0432]: unresolved import `tokio::net::UnixStream`
  --> crates\atuin-daemon\src\client.rs:2:5
   |
2  | use tokio::net::UnixStream;
   |     ^^^^^^^^^^^^^^^^^^^^^^ no `UnixStream` in `net`
   |
note: found an item that was configured out
  --> C:/Develop/msys64/home/Christian/.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.36.0\src\net\mod.rs:51:27
   |
51 |     pub use unix::stream::UnixStream;
   |                           ^^^^^^^^^^

error[E0432]: unresolved import `tokio::net::UnixListener`
  --> crates\atuin-daemon\src\server.rs:16:5
   |
16 | use tokio::net::UnixListener;
   |     ^^^^^^^^^^^^------------
   |     |           |
   |     |           help: a similar name exists in the module: `TcpListener`
   |     no `UnixListener` in `net`
   |
note: found an item that was configured out
  --> C:/Develop/msys64/home/Christian/.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.36.0\src\net\mod.rs:50:29
   |
50 |     pub use unix::listener::UnixListener;
   |                             ^^^^^^^^^^^^

error[E0432]: unresolved import `tokio_stream::wrappers::UnixListenerStream`
  --> crates\atuin-daemon\src\server.rs:17:5
   |
17 | use tokio_stream::wrappers::UnixListenerStream;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^------------------
   |     |                       |
   |     |                       help: a similar name exists in the module: `TcpListenerStream`
   |     no `UnixListenerStream` in `wrappers`
   |
note: found an item that was configured out
  --> C:/Develop/msys64/home/Christian/.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-stream-0.1.14\src\wrappers.rs:48:28
   |
48 |     pub use unix_listener::UnixListenerStream;
   |                            ^^^^^^^^^^^^^^^^^^

error[E0433]: failed to resolve: could not find `unix` in `signal`
   --> crates\atuin-daemon\src\server.rs:138:35
    |
138 |     let mut term = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
    |                                   ^^^^ could not find `unix` in `signal`
    |
note: found an item that was configured out
   --> C:/Develop/msys64/home/Christian/.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.36.0\src\signal\mod.rs:61:9
    |
61  | pub mod unix;
    |         ^^^^

error[E0433]: failed to resolve: could not find `unix` in `signal`
   --> crates\atuin-daemon\src\server.rs:138:63
    |
138 |     let mut term = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
    |                                                               ^^^^ could not find `unix` in `signal`
    |
note: found an item that was configured out
   --> C:/Develop/msys64/home/Christian/.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.36.0\src\signal\mod.rs:61:9
    |
61  | pub mod unix;
    |         ^^^^

error[E0433]: failed to resolve: could not find `unix` in `signal`
   --> crates\atuin-daemon\src\server.rs:140:34
    |
140 |     let mut int = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())
    |                                  ^^^^ could not find `unix` in `signal`
    |
note: found an item that was configured out
   --> C:/Develop/msys64/home/Christian/.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.36.0\src\signal\mod.rs:61:9
    |
61  | pub mod unix;
    |         ^^^^

error[E0433]: failed to resolve: could not find `unix` in `signal`
   --> crates\atuin-daemon\src\server.rs:140:62
    |
140 |     let mut int = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())
    |                                                              ^^^^ could not find `unix` in `signal`
    |
note: found an item that was configured out
   --> C:/Develop/msys64/home/Christian/.cargo\registry\src\index.crates.io-6f17d22bba15001f\tokio-1.36.0\src\signal\mod.rs:61:9
    |
61  | pub mod unix;
    |         ^^^^

Some errors have detailed explanations: E0432, E0433.
For more information about an error, try `rustc --explain E0432`.
error: could not compile `atuin-daemon` (lib) due to 7 previous errors
```

Tested on `x86_64-pc-windows-gnu` and `x86_64-pc-windows-msvc`.

### Atuin doctor output

```yaml
atuin:
  version: 18.2.0
  sync:
    cloud: false
    records: true
    auto_sync: true
    last_sync: 2024-05-08 17:54:15.2530676 +00:00:00
shell:
  name: zsh.exe
  default: unknown
  plugins:
  - atuin
system:
  os: Windows
  arch: x86
  version: 11 (22621)
  disks:
  - name: Zeh
    filesystem: NTFS
```


### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion / hints:
It happens because the crate `atuin-daemon` gets built regardless of the feature `daemon`.

I tried making `atuin-daemon` `optional` and dependent on the feature `daemon` in `atuin`'s `Cargo.toml` like so:

```
...
[features]
....
daemon = ["atuin-daemon", "atuin-client/daemon"]
...

[dependencies]
...
atuin-daemon = { path = "../atuin-daemon", version = "0.1.0", optional = true }
...
```

However `atuin-daemon` still gets built (without the feature `daemon` enabled) and the errors are the same. Maybe my understanding of how `optional` and features that enable dependecies work is wrong.

Fix patch:
diff --git a/README.md b/README.md
index 64f126b89a2..1bff59ec791 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@
 Atuin replaces your existing shell history with a SQLite database, and records
 additional context for your commands. Additionally, it provides optional and
 _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
 - nushell
 - xonsh
- 
+
 ## Community
 
 ### 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)
 
 # Quickstart
-  
+
 ## 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!
-  
+
 Read more below for offline-only usage, or for hosting your own server.
 
 ```
@@ -130,10 +130,10 @@ Then restart your shell!
 > section below.
 
 ## Offline only (no sync)
-  
+
 ```
 bash <(curl https://raw.githubusercontent.com/atuinsh/atuin/main/install.sh)
-            
+
 atuin import auto
 ```
 
@@ -171,25 +171,28 @@ toolchain, then you can run:
 ```
 cargo install atuin
 ```
-  
+
 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
 
 ```
 brew install atuin
 ```
-  
+
 And then follow [the shell setup](#shell-plugin)
-  
+
 ### 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
 ```
-  
+
 And then follow [the shell setup](#shell-plugin)
 
 ### Cave
@@ -223,7 +226,7 @@ Atuin is available in the Arch Linux [[extra] repository](https://archlinux.org/
 ```
 pacman -S atuin
 ```
-  
+
 And then follow [the shell setup](#shell-plugin)
 
 ### Xbps
@@ -243,7 +246,7 @@ Atuin is available in the Termux package repository:
 ```
 pkg install atuin
 ```
-  
+
 And then follow [the shell setup](#shell-plugin)
 
 ### From source
@@ -253,7 +256,7 @@ git clone https://github.com/atuinsh/atuin.git
 cd atuin/crates/atuin
 cargo install --path .
 ```
-  
+
 And then follow [the shell setup](#shell-plugin)
 
 ## Shell plugin
@@ -273,9 +276,9 @@ echo 'eval "$(atuin init zsh)"' >> ~/.zshrc
 zinit load atuinsh/atuin
 ```
 
-#### Antigen  
-  
-```sh  
+#### Antigen
+
+```sh
 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
-  
+
 ### Nushell
 
 Run in *Nushell*:
@@ -371,7 +374,7 @@ to the end of your `~/.xonshrc`
 
 # 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
 
diff --git a/crates/atuin-client/config.toml b/crates/atuin-client/config.toml
index 50db6952832..1de7308c2ef 100644
--- a/crates/atuin-client/config.toml
+++ b/crates/atuin-client/config.toml
@@ -210,3 +210,18 @@ records = true
 ## auto: length of the selected command.
 ## static: length of the longest command stored in the history.
 # 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
diff --git a/crates/atuin-client/src/settings.rs b/crates/atuin-client/src/settings.rs
index ad7f95fc6b6..ed1c0a29d04 100644
--- a/crates/atuin-client/src/settings.rs
+++ b/crates/atuin-client/src/settings.rs
@@ -353,6 +353,9 @@ pub struct Daemon {
 
     /// The path to the unix socket used by the daemon
     pub socket_path: String,
+
+    /// The port that should be used for TCP on non unix systems
+    pub tcp_port: u64,
 }
 
 impl Default for Preview {
@@ -369,6 +372,7 @@ impl Default for Daemon {
             enabled: false,
             sync_frequency: 300,
             socket_path: "".to_string(),
+            tcp_port: 8889,
         }
     }
 }
@@ -706,6 +710,7 @@ impl Settings {
             .set_default("daemon.sync_frequency", 300)?
             .set_default("daemon.enabled", false)?
             .set_default("daemon.socket_path", socket_path.to_str())?
+            .set_default("daemon.tcp_port", 8889)?
             .set_default(
                 "prefers_reduced_motion",
                 std::env::var("NO_MOTION")
diff --git a/crates/atuin-daemon/src/client.rs b/crates/atuin-daemon/src/client.rs
index a832f9a9543..f3fecdbc199 100644
--- a/crates/atuin-daemon/src/client.rs
+++ b/crates/atuin-daemon/src/client.rs
@@ -1,8 +1,12 @@
 use eyre::{eyre, Result};
-use tokio::net::UnixStream;
+#[cfg(windows)]
+use tokio::net::TcpStream;
 use tonic::transport::{Channel, Endpoint, Uri};
 use tower::service_fn;
 
+#[cfg(unix)]
+use tokio::net::UnixStream;
+
 use atuin_client::history::History;
 
 use crate::history::{
@@ -15,6 +19,7 @@ pub struct HistoryClient {
 
 // Wrap the grpc client
 impl HistoryClient {
+    #[cfg(unix)]
     pub async fn new(path: String) -> Result<Self> {
         let channel = Endpoint::try_from("http://atuin_local_daemon:0")?
             .connect_with_connector(service_fn(move |_: Uri| {
@@ -30,6 +35,21 @@ impl HistoryClient {
         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> {
         let req = StartHistoryRequest {
             command: h.command,
diff --git a/crates/atuin-daemon/src/server.rs b/crates/atuin-daemon/src/server.rs
index 72305737f7f..77824f60c79 100644
--- a/crates/atuin-daemon/src/server.rs
+++ b/crates/atuin-daemon/src/server.rs
@@ -13,8 +13,6 @@ use atuin_client::database::{Database, Sqlite as HistoryDatabase};
 use atuin_client::history::{History, HistoryId};
 use dashmap::DashMap;
 use eyre::Result;
-use tokio::net::UnixListener;
-use tokio_stream::wrappers::UnixListenerStream;
 use tonic::{transport::Server, Request, Response, Status};
 
 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) {
     let mut term = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
         .expect("failed to register sigterm handler");
@@ -150,6 +149,52 @@ async fn shutdown_signal(socket: PathBuf) {
     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
 
 /// Listen on a unix socket
@@ -168,12 +213,6 @@ pub async fn listen(
 
     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
     tokio::spawn(sync::worker(
         settings.clone(),
@@ -182,10 +221,5 @@ pub async fn listen(
         history_db,
     ));
 
-    Server::builder()
-        .add_service(HistoryServer::new(history))
-        .serve_with_incoming_shutdown(uds_stream, shutdown_signal(socket.into()))
-        .await?;
-
-    Ok(())
+    start_server(settings, history).await
 }
diff --git a/crates/atuin/src/command/client/history.rs b/crates/atuin/src/command/client/history.rs
index f966c30294c..9d1453fc3e5 100644
--- a/crates/atuin/src/command/client/history.rs
+++ b/crates/atuin/src/command/client/history.rs
@@ -315,11 +315,15 @@ impl Cmd {
         }
 
         if settings.daemon.enabled {
-            let resp =
-                atuin_daemon::client::HistoryClient::new(settings.daemon.socket_path.clone())
-                    .await?
-                    .start_history(h)
-                    .await?;
+            let resp = atuin_daemon::client::HistoryClient::new(
+                #[cfg(not(unix))]
+                settings.daemon.tcp_port,
+                #[cfg(unix)]
+                settings.daemon.socket_path.clone(),
+            )
+            .await?
+            .start_history(h)
+            .await?;
 
             // print the ID
             // 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.
         // At the very least, while this is opt-in
         if settings.daemon.enabled {
-            atuin_daemon::client::HistoryClient::new(settings.daemon.socket_path.clone())
-                .await?
-                .end_history(id.to_string(), duration.unwrap_or(0), exit)
-                .await?;
+            let resp = atuin_daemon::client::HistoryClient::new(
+                #[cfg(not(unix))]
+                settings.daemon.tcp_port,
+                #[cfg(unix)]
+                settings.daemon.socket_path.clone(),
+            )
+            .await?
+            .end_history(id.to_string(), duration.unwrap_or(0), exit)
+            .await?;
 
             return Ok(());
         }


Test patch: