1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
//! `roguewave` is an imperative remote server automation tool.
//! It allows you to create deployment scripts and automate repetitive
//! administration tasks.
//!
//! Unlike many existing tools that achieve similar functionality,
//! `roguewave` is not based on declarative descriptions and configuration files.
//! It's a code-first tool, where you use Rust code to describe any process
//! you implement. This gives you clear control flow, explicit context passing,
//! simple code deduplication and many more benefits that come with using a modern
//! high-level language. This also makes deployment and server configuration
//! more approachable to developers.
//!
//! `roguewave` doesn't come with many built-in capabilities. Existing built-ins are more like
//! starting points or examples of what you can achieve, and they make some assumptions
//! about the remote system that are not universally true (e.g. root access, availability of
//! `sudo` and `apt`). However, `roguewave` itself can be used with any remote system
//! that provides SSH and SFTP access.
//!
//! Instead of relying on built-ins completely, users are encouraged
//! to create and reuse utility functions that suit their purposes. These utilities can
//! be shared with others as Rust crates or suggested for merging into `roguewave`.
//!
//! # Getting started
//!
//! First, make sure you can connect to your server via SSH without a password. Typically,
//! you can achieve that by setting up keypair auth and adding your key to ssh-agent.
//! Next, you can create a `roguewave` session like this:
//! ```no_run
//! use roguewave::Session;
//! use std::env;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! if env::var("RUST_LOG").is_err() {
//! env::set_var("RUST_LOG", "info");
//! }
//! env_logger::init(); // initialize logger
//! let mut session = Session::connect("username@hostname").await?;
//! //...
//! Ok(())
//! }
//! ```
//!
//! The `Session` handle provides access to built-in helpers:
//! ```no_run
//! # use roguewave::Session;
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! # let mut session = Session::connect("username@hostname").await?;
//! session.apt().install(&["nginx"]).await?;
//! session.create_user("alice").await?;
//! session.fs().write("/home/username/.bashrc", "export PAGER=less\n").await?;
//! # Ok(())
//! # }
//! ```
//! You can also run arbitrary commands:
//! ```no_run
//! # use roguewave::Session;
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! # let mut session = Session::connect("username@hostname").await?;
//! session.command(["systemctl", "restart", "nginx"]).run().await?;
//! let uname = session.command(["uname", "-a"]).run().await?.stdout;
//! # Ok(())
//! # }
//! ```
//!
//! # Extending `roguewave`
//!
//! The simplest way to write a custom helper is to create a function:
//! ```
//! use roguewave::Session;
//!
//! async fn setup_user(session: &mut Session, name: &str) -> anyhow::Result<()> {
//! session.create_user(name).await?;
//! let home_dir = session.home_dir(Some(name)).await?;
//! session.upload(["important_file.txt"], &home_dir, Some(name)).await?;
//! Ok(())
//! }
//! ```
//! You can create a nicer interface by creating an extension trait:
//! ```
//! use roguewave::Session;
//!
//! #[async_trait::async_trait]
//! pub trait SetupUser {
//! async fn setup_user(&mut self, name: &str) -> anyhow::Result<()>;
//! }
//!
//! #[async_trait::async_trait]
//! impl SetupUser for Session {
//! async fn setup_user(&mut self, name: &str) -> anyhow::Result<()> {
//! todo!()
//! }
//! }
//! ```
//! Alternatively, you can create a struct that provides access to multiple helpers:
//! ```
//! use roguewave::Session;
//!
//! pub struct Cron<'a>(&'a mut Session);
//! pub trait GetCron {
//! fn cron(&mut self) -> Cron;
//! }
//!
//! impl GetCron for Session {
//! fn cron(&mut self) -> Cron {
//! Cron(self)
//! }
//! }
//!
//! impl Cron<'_> {
//! async fn add_task(&mut self, name: &str) -> anyhow::Result<()> {
//! todo!()
//! }
//! async fn remove_task(&mut self, name: &str) -> anyhow::Result<()> {
//! todo!()
//! }
//! }
//! ```
use ;
use Context;
use ;
use ;
use TypeMap;
pub use ;
pub use LocalCommand;
pub use ;
/// A SSH session to a remote host.