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
//! # Virtx
//!
//! Run tasks in disposable Linux VMs from your own code: what a task reads, and what it runs.
//!
//! Both are ordinary on purpose: a script, a build or a model handed a filesystem and a shell
//! already knows both interfaces, and every added capability arrives as a file to read or a
//! command to run.
//!
//! * **What it reads is a filesystem**, with `cat`, `grep` or anything else once a binding
//! mounts it on the host.
//! * **What it runs is commands**, each an argv sent over one channel, with everything the
//! command wrote coming back.
//!
//! ## Quickstart
//!
//! Needs the `mount` feature. On Linux the binding is `FuseMount`; macOS uses `FuseTMount` and
//! Windows `DokanMount`, with nothing else changed.
//!
//! ```ignore
//! use std::path::Path;
//!
//! use virtx::console::ConsoleClient;
//! use virtx::fs::{Directory, FuseMount};
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! // What the task reads: an in-memory file beside a host directory.
//! let context = Directory::new()
//! .with_file("notes/today.md", "ship the release".as_bytes())?
//! .with_mount("project", "/home/me/project")?;
//!
//! // Constructing the guard mounts, dropping it unmounts; the console holds it for the session.
//! let mount = FuseMount::try_new(context, Path::new("/tmp/session"))?;
//!
//! // What the task runs. A session's shape, including where each tree appears to
//! // commands, is fixed when the console is built.
//! let mut console = ConsoleClient::builder()
//! .mount(mount, "/work")
//! .build()
//! .await?;
//!
//! let result = console.exec(["sh", "-c", "wc -w /work/notes/today.md"], None).await?;
//! println!("{}", String::from_utf8_lossy(&result.stdout));
//! Ok(())
//! }
//! ```
//!
//! ## Structure
//!
//! * [`fs`]: what a session reads and writes. Stores, the in-memory [`Directory`](fs::Directory), and the
//! bindings that mount them on the host.
//! * [`console`]: what a session runs. [`ConsoleClient`](console::ConsoleClient) runs commands
//! in a session, which [`Relay`](console::Relay) shares with another native module in the
//! same process (ailoy's, say).
//! * [`protocol`]: the wire between a console client and a console server. The server is a
//! prebuilt binary, `virtx-uvm`, that `ensure_virtx` fetches into the cache's `bin`.
//! * [`image`]: the images a session runs on, declared or built ahead of time.
//!
//! [`fs`] and [`console`] meet only at [`Mount`](fs::Mount): a console takes trees already
//! mounted and never builds one or touches a binding, and [`fs`] does not know a console
//! exists. There is no shared error type: [`fs`] answers in [`std::io::Error`], classified by
//! kind, and [`console`] in [`Failure`](protocol::Failure), or [`anyhow::Error`] while building.
//!
//! ## The file layout
//!
//! A module with submodules is a directory whose `mod.rs` holds its documentation and
//! re-exports, and whose siblings hold the code, including one named after the module for its
//! main type (`message/message.rs` has [`Message`](protocol::Message)); hence
//! `module_inception` is allowed.
pub use ensure_virtx;
/// What every waiting method of a [`Client`] or [`Server`] returns.
///
/// Re-exported so implementors can name it without depending on `futures_core`.
///
/// [`Client`]: protocol::Client
/// [`Server`]: protocol::Server
pub use BoxFuture;
/// Everything virtx keeps on this host, under one root: `$VIRTX_HOME`, or
/// `virtx` under the user's cache directory.
///
/// One root, so a host has a single answer to "where does this go": contents are split by
/// owner as paths under it. This crate keeps only `bin/`, where the console server lives.
///
/// The user's cache directory is `XDG_CACHE_HOME` or `~/.cache`, on macOS
/// `~/Library/Caches`, and on Windows `%LOCALAPPDATA%`, falling back to
/// `%USERPROFILE%\AppData\Local` when that is unset.