Checkpoint
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/rust
|
||||||
|
{
|
||||||
|
"name": "Rust",
|
||||||
|
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/rust:2-1-trixie",
|
||||||
|
|
||||||
|
// Use 'mounts' to make the cargo cache persistent in a Docker Volume.
|
||||||
|
// "mounts": [
|
||||||
|
// {
|
||||||
|
// "source": "devcontainer-cargo-cache-${devcontainerId}",
|
||||||
|
// "target": "/usr/local/cargo",
|
||||||
|
// "type": "volume"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
|
||||||
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
|
// "features": {},
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
// "forwardPorts": [],
|
||||||
|
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
"postCreateCommand": "rustup component add rustfmt --toolchain nightly",
|
||||||
|
|
||||||
|
// Configure tool-specific properties.
|
||||||
|
// "customizations": {},
|
||||||
|
|
||||||
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||||
|
"remoteUser": "root"
|
||||||
|
}
|
||||||
Generated
+3875
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
|||||||
|
[workspace]
|
||||||
|
members = ["nye-terminal", "nye-daemon", "nye-wire-protocol", "nye-console", "nye-package"]
|
||||||
|
resolver = "3"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "nye-console"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
colored = "3.1.1"
|
||||||
|
dialoguer = "0.12.0"
|
||||||
|
indicatif = { version = "0.18.6", features = ["tokio"] }
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use colored::Colorize;
|
||||||
|
use dialoguer::theme::Theme;
|
||||||
|
use dialoguer::{Input, Select};
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
|
||||||
|
/// Takes a result and logs it if it's an error.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `result` - The result.
|
||||||
|
pub fn catch_err<T, E>(result: Result<T, E>)
|
||||||
|
where
|
||||||
|
E: Display,
|
||||||
|
{
|
||||||
|
if let Err(error) = result {
|
||||||
|
err(error.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays a spinner loading bar on the console.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `message` - The message to display with the spinner.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`ProgressBar`] - The spinner bar already being displayed on the console.
|
||||||
|
pub fn spinner(message: impl Into<String>) -> ProgressBar {
|
||||||
|
let bar = ProgressBar::new_spinner()
|
||||||
|
.with_message(message.into())
|
||||||
|
.with_style(
|
||||||
|
ProgressStyle::default_spinner()
|
||||||
|
.tick_strings(&["[\\]", "[|]", "[/]", "[-]", "^w^"])
|
||||||
|
.template("{spinner:.cyan} {msg}")
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
bar.enable_steady_tick(Duration::from_millis(200));
|
||||||
|
|
||||||
|
bar
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays a bytes progress bar on the console.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `message` - The message to display with the progress bar.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`ProgressBar`] - The progress bar with a ticking spinner.
|
||||||
|
pub fn progress_bytes(message: impl Into<String>, total: u64) -> ProgressBar {
|
||||||
|
let bar = ProgressBar::new(total)
|
||||||
|
.with_message(message.into())
|
||||||
|
.with_style(
|
||||||
|
ProgressStyle::default_bar()
|
||||||
|
.progress_chars("##")
|
||||||
|
.tick_strings(&["[\\]", "[|]", "[/]", "[-]", "^w^"])
|
||||||
|
.template(
|
||||||
|
"{spinner:.cyan} {wide_msg} {bar:.cyan/black} {binary_bytes}/{binary_total_bytes}",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
bar.enable_steady_tick(Duration::from_millis(200));
|
||||||
|
|
||||||
|
bar
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays a success log message.
|
||||||
|
///
|
||||||
|
/// Use this when finishing a task to indicate success.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `message` - The message to log.
|
||||||
|
pub fn yay(message: impl Into<String>) {
|
||||||
|
let prefix = "YAY".cyan();
|
||||||
|
let message = message.into();
|
||||||
|
|
||||||
|
println!("{prefix} {message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays an info log message.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `message` - The message to log.
|
||||||
|
pub fn info(message: impl Into<String>) {
|
||||||
|
let prefix = "III".blue();
|
||||||
|
let message = message.into();
|
||||||
|
|
||||||
|
println!("{prefix} {message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays a warning log message.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `message` - The message to log.
|
||||||
|
pub fn warn(message: impl Into<String>) {
|
||||||
|
let prefix = "WWW".yellow();
|
||||||
|
let message = message.into();
|
||||||
|
|
||||||
|
println!("{prefix} {message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays an error log message.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `message` - The message to log.
|
||||||
|
pub fn err(message: impl Into<String>) {
|
||||||
|
let prefix = "ERR".red();
|
||||||
|
let message = message.into();
|
||||||
|
|
||||||
|
println!("{prefix} {message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A theme for the dialoguer crate that is used to prompt the user for input and selection.
|
||||||
|
struct InputTheme;
|
||||||
|
|
||||||
|
impl Theme for InputTheme {
|
||||||
|
fn format_input_prompt(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn std::fmt::Write,
|
||||||
|
prompt: &str,
|
||||||
|
_default: Option<&str>,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
let prefix = ">>>".purple();
|
||||||
|
|
||||||
|
write!(f, "{prefix} {prompt}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_input_prompt_selection(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn std::fmt::Write,
|
||||||
|
prompt: &str,
|
||||||
|
sel: &str,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
let prefix = ">>>".purple();
|
||||||
|
|
||||||
|
write!(f, "{prefix} {prompt}{sel}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_select_prompt(&self, f: &mut dyn std::fmt::Write, prompt: &str) -> std::fmt::Result {
|
||||||
|
let prefix = ">>>".purple();
|
||||||
|
|
||||||
|
write!(f, "{prefix} {prompt}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_select_prompt_item(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn std::fmt::Write,
|
||||||
|
text: &str,
|
||||||
|
active: bool,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
if active {
|
||||||
|
write!(f, "{}", format!(" * {text}").purple())
|
||||||
|
} else {
|
||||||
|
write!(f, " {text}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_select_prompt_selection(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn std::fmt::Write,
|
||||||
|
prompt: &str,
|
||||||
|
sel: &str,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
let prefix = ">>>".purple();
|
||||||
|
|
||||||
|
write!(f, "{prefix} {prompt}{}", sel.purple())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prompts the user for input.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `prompt` - The message to prompt the user with.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(String)` - The user's input.
|
||||||
|
/// * `Err(Error)` - If the user cancelled the operation or if the user could not be prompted.
|
||||||
|
pub fn input(prompt: impl Into<String>) -> Result<String, dialoguer::Error> {
|
||||||
|
Input::with_theme(&InputTheme)
|
||||||
|
.with_prompt(prompt)
|
||||||
|
.interact_text()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prompts the user for input with a default value.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `prompt` - The message to prompt the user with.
|
||||||
|
/// * `default` - The default value.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(String)` - The user's input.
|
||||||
|
/// * `Err(Error)` - If the user cancelled the operation or if the user could not be prompted.
|
||||||
|
pub fn input_with_default(
|
||||||
|
prompt: impl Into<String>,
|
||||||
|
default: impl Into<String>,
|
||||||
|
) -> Result<String, dialoguer::Error> {
|
||||||
|
Input::with_theme(&InputTheme)
|
||||||
|
.with_prompt(prompt)
|
||||||
|
.default(default.into())
|
||||||
|
.interact_text()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prompts the user to select an option.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `prompt` - The message to prompt the user with.
|
||||||
|
/// * `options` - The options to give the user for selection.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(usize)` - The index of the option selected by the user.
|
||||||
|
/// * `Err(Error)` - If the user cancelled the operation or if the user could not be prompted.
|
||||||
|
pub fn select(
|
||||||
|
prompt: impl Into<String>,
|
||||||
|
options: impl IntoIterator<Item = impl ToString>,
|
||||||
|
) -> Result<usize, dialoguer::Error> {
|
||||||
|
Select::with_theme(&InputTheme)
|
||||||
|
.with_prompt(prompt)
|
||||||
|
.items(options)
|
||||||
|
.interact()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prompts the user to select an option with an option selected by default.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `prompt` - The message to prompt the user with.
|
||||||
|
/// * `options` - The options to give the user for selection.
|
||||||
|
/// * `default` - The index of the default option.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(usize)` - The index of the option selected by the user.
|
||||||
|
/// * `Err(Error)` - If the user cancelled the operation or if the user could not be prompted.
|
||||||
|
pub fn select_with_default(
|
||||||
|
prompt: impl Into<String>,
|
||||||
|
options: impl IntoIterator<Item = impl ToString>,
|
||||||
|
default: usize,
|
||||||
|
) -> Result<usize, dialoguer::Error> {
|
||||||
|
Select::with_theme(&InputTheme)
|
||||||
|
.with_prompt(prompt)
|
||||||
|
.items(options)
|
||||||
|
.default(default)
|
||||||
|
.interact()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prompts the user to select an option with an option selected by default, without reporting the
|
||||||
|
/// selection.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `prompt` - The message to prompt the user with.
|
||||||
|
/// * `options` - The options to give the user for selection.
|
||||||
|
/// * `default` - The index of the default option.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(usize)` - The index of the option selected by the user.
|
||||||
|
/// * `Err(Error)` - If the user cancelled the operation or if the user could not be prompted.
|
||||||
|
pub fn select_with_default_without_report(
|
||||||
|
prompt: impl Into<String>,
|
||||||
|
options: impl IntoIterator<Item = impl ToString>,
|
||||||
|
default: usize,
|
||||||
|
) -> Result<usize, dialoguer::Error> {
|
||||||
|
Select::with_theme(&InputTheme)
|
||||||
|
.with_prompt(prompt)
|
||||||
|
.items(options)
|
||||||
|
.default(default)
|
||||||
|
.report(false)
|
||||||
|
.interact()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats items into a human-readable list.
|
||||||
|
///
|
||||||
|
/// For example,
|
||||||
|
/// - `[1] => "1"`
|
||||||
|
/// - `[1, 2] => "1 and 2"`
|
||||||
|
/// - `[1, 2, 3] => "1, 2, and 3"`.
|
||||||
|
/// - `[1, 2, 3, 4] => "1, 2, 3, and 4"`.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `items` - The items of the list.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`String`] -> The formatted list.
|
||||||
|
pub fn list<T>(items: &[T]) -> String
|
||||||
|
where
|
||||||
|
T: Display,
|
||||||
|
{
|
||||||
|
let mut string = String::new();
|
||||||
|
|
||||||
|
for (i, item) in items.iter().enumerate() {
|
||||||
|
let is_first = i == 0;
|
||||||
|
let is_penultimate = i == items.len() - 2;
|
||||||
|
let is_last = i == items.len() - 1;
|
||||||
|
|
||||||
|
match (is_first, is_penultimate, is_last) {
|
||||||
|
(false, false, false) => string.push_str(&format!("{item}, ")),
|
||||||
|
(false, false, true) => string.push_str(&item.to_string()),
|
||||||
|
(false, true, false) => string.push_str(&format!("{item}, and ")),
|
||||||
|
(false, true, true) => unreachable!(),
|
||||||
|
(true, false, false) => string.push_str(&format!("{item}, ")),
|
||||||
|
(true, false, true) => string.push_str(&item.to_string()),
|
||||||
|
(true, true, false) => string.push_str(&format!("{item} and ")),
|
||||||
|
(true, true, true) => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "nye-daemon"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.6.5", features = ["wrap_help", "derive", "env"] }
|
||||||
|
tokio = { version = "1.53.1", features = ["full"] }
|
||||||
|
nye-wire-protocol = { version = "0.1.0", path = "../nye-wire-protocol" }
|
||||||
|
tracing = { version = "0.1.44", features = ["async-await"] }
|
||||||
|
anyhow = { version = "1.0.104", features = ["backtrace"] }
|
||||||
|
uzers = "0.12.2"
|
||||||
|
nye-console = { version = "0.1.0", path = "../nye-console" }
|
||||||
|
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||||
|
toasty = { version = "0.9.0", features = ["turso"] }
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#[derive(Debug, clap::Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
/// Whether to enable debug logging.
|
||||||
|
///
|
||||||
|
/// When enabled, the daemon will log additional information to help with debugging.
|
||||||
|
#[arg(short, long, default_value_t = false)]
|
||||||
|
pub debug: bool,
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod setup;
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use nye_wire_protocol::messages::MessageResponse;
|
||||||
|
use nye_wire_protocol::{Connection, Stream};
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
use crate::utils::LinuxUser;
|
||||||
|
|
||||||
|
pub async fn run(connection: &mut Connection, stream: &mut Stream) -> anyhow::Result<()> {
|
||||||
|
let user =
|
||||||
|
LinuxUser::from_connection(connection).context("Could not get the user to set up.")?;
|
||||||
|
|
||||||
|
setup(SetupKind::User(user.clone()))
|
||||||
|
.await
|
||||||
|
.context("Could not set up the user.")?;
|
||||||
|
|
||||||
|
stream
|
||||||
|
.send(MessageResponse::Setup)
|
||||||
|
.await
|
||||||
|
.context("Could not send setup completion notification to the client.")?;
|
||||||
|
|
||||||
|
tracing::trace!(
|
||||||
|
user.name,
|
||||||
|
user.uid,
|
||||||
|
user.gid,
|
||||||
|
"Sent setup completion notification to the client"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the type of setup to perform.
|
||||||
|
pub enum SetupKind {
|
||||||
|
/// Sets up the Nye package manager for a specific user.
|
||||||
|
User(LinuxUser),
|
||||||
|
|
||||||
|
/// Sets up the Nye package manager for the entire system.
|
||||||
|
System,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SetupKind {
|
||||||
|
/// Returns the root directory for the setup kind.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`PathBuf`] - The root directory for the setup kind.
|
||||||
|
fn root(&self) -> PathBuf {
|
||||||
|
match self {
|
||||||
|
SetupKind::User(user) => format!("/usr/{}/nye", user.name).into(),
|
||||||
|
SetupKind::System => "/nye".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets up the environment for the given setup kind.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `kind` - The kind of setup to perform.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(())` - if the setup was successful.
|
||||||
|
/// * `Err(anyhow::Error)` - if there was an error during setup.
|
||||||
|
pub async fn setup(kind: SetupKind) -> anyhow::Result<()> {
|
||||||
|
let dirs = [
|
||||||
|
kind.root(),
|
||||||
|
kind.root().join("packages"),
|
||||||
|
kind.root().join("volumes"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for dir in dirs {
|
||||||
|
fs::create_dir_all(&dir)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Could not create directories at {}", dir.display()))?;
|
||||||
|
|
||||||
|
tracing::debug!("Created directories at {}", dir.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"The Nye package manager was successfully set up at {}",
|
||||||
|
kind.root().display()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
use anyhow::Context;
|
||||||
|
use nye_wire_protocol::Connection;
|
||||||
|
use nye_wire_protocol::messages::MessageRequest;
|
||||||
|
use tokio::fs;
|
||||||
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
|
use crate::commands;
|
||||||
|
use crate::utils::LinuxUser;
|
||||||
|
|
||||||
|
/// Listens for incoming connections from clients and handles them.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `tasks` - A mutable reference to a `JoinSet` that will be used to spawn tasks for handling
|
||||||
|
/// incoming connections.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `!` - This function never returns, as it runs an infinite loop accepting incoming connections
|
||||||
|
/// and spawning tasks to handle them.
|
||||||
|
/// * `Err(anyhow::Error)` - if there was an error binding to the socket or accepting an incoming
|
||||||
|
/// connection.
|
||||||
|
pub async fn run(tasks: &mut JoinSet<anyhow::Result<()>>) -> anyhow::Result<()> {
|
||||||
|
let listener = nye_wire_protocol::listen("/run/nye-packages.sock")
|
||||||
|
.await
|
||||||
|
.context("Could not bind to the socket at /run/nye-packages.sock")?;
|
||||||
|
|
||||||
|
tracing::info!("Listening for incoming connections on /run/nye-packages.sock");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let connection = listener
|
||||||
|
.accept()
|
||||||
|
.await
|
||||||
|
.context("There was an error accepting an incoming connection.")?;
|
||||||
|
|
||||||
|
tasks.spawn(async move {
|
||||||
|
let result = handle_connection(connection).await;
|
||||||
|
|
||||||
|
if let Err(e) = &result {
|
||||||
|
eprintln!("{e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle a connection from a client.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `connection` - The connection to handle.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(())` - if the connection was handled successfully.
|
||||||
|
/// * `Err(anyhow::Error)` - if there was an error handling the connection.
|
||||||
|
async fn handle_connection(mut connection: Connection) -> anyhow::Result<()> {
|
||||||
|
let user = LinuxUser::from_ucred(connection.peer_credentials())?;
|
||||||
|
|
||||||
|
tracing::info!(user.name, user.uid, user.gid, "Accepted a connection",);
|
||||||
|
|
||||||
|
let mut stream = connection
|
||||||
|
.recv_stream()
|
||||||
|
.await
|
||||||
|
.context("Could not receive an incoming stream from the client.")?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
user.name,
|
||||||
|
user.uid,
|
||||||
|
user.gid,
|
||||||
|
stream.id = stream.id(),
|
||||||
|
"Accepted a stream",
|
||||||
|
);
|
||||||
|
|
||||||
|
let request = stream
|
||||||
|
.recv_request()
|
||||||
|
.await
|
||||||
|
.context("Could not receive an incoming message from the incoming stream.")?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
user.name,
|
||||||
|
user.uid,
|
||||||
|
user.gid,
|
||||||
|
stream.id = stream.id(),
|
||||||
|
message.kind = ?request.kind(),
|
||||||
|
"Received a message"
|
||||||
|
);
|
||||||
|
|
||||||
|
match request {
|
||||||
|
MessageRequest::Setup => {
|
||||||
|
commands::setup::run(&mut connection, &mut stream).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clean up the socket file at `/run/nye-packages.sock`.
|
||||||
|
///
|
||||||
|
/// This function is called when the daemon is shutting down to remove the socket file.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(())` - if the socket file was removed successfully.
|
||||||
|
/// * `Err(anyhow::Error)` - if there was an error removing the socket file.
|
||||||
|
pub async fn cleanup() -> anyhow::Result<()> {
|
||||||
|
fs::remove_file("/run/nye-packages.sock")
|
||||||
|
.await
|
||||||
|
.context("Could not remove the socket at /run/nye-packages.sock")
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
use anyhow::Context;
|
||||||
|
use clap::Parser;
|
||||||
|
use tokio::signal;
|
||||||
|
use tokio::task::JoinSet;
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
use crate::args::Args;
|
||||||
|
use crate::commands::setup::SetupKind;
|
||||||
|
|
||||||
|
mod args;
|
||||||
|
mod commands;
|
||||||
|
mod db;
|
||||||
|
mod listener;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let user_id = uzers::get_current_uid();
|
||||||
|
|
||||||
|
if user_id != 0 {
|
||||||
|
anyhow::bail!(concat!(
|
||||||
|
"The daemon must be run as root, but the current user is not root. ",
|
||||||
|
"Please run the daemon as root."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let env_filter = if args.debug {
|
||||||
|
EnvFilter::new("off,nye_daemon=trace,nye_wire_protocol=trace")
|
||||||
|
} else {
|
||||||
|
EnvFilter::new("off,nye_daemon=info,nye_wire_protocol=warn")
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(env_filter)
|
||||||
|
.with_target(false)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
commands::setup::setup(SetupKind::System)
|
||||||
|
.await
|
||||||
|
.context("Could not set up the Nye package manager's system-wide configuration.")?;
|
||||||
|
|
||||||
|
let mut tasks = JoinSet::new();
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = listener::run(&mut tasks) => {}
|
||||||
|
_ = signal::ctrl_c() => {
|
||||||
|
tasks.join_all().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listener::cleanup()
|
||||||
|
.await
|
||||||
|
.context("Failed to cleanup socket listener.")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use anyhow::Context;
|
||||||
|
use nye_wire_protocol::Connection;
|
||||||
|
use tokio::net::unix::UCred;
|
||||||
|
|
||||||
|
/// A struct representing a Linux user.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LinuxUser {
|
||||||
|
pub uid: u32,
|
||||||
|
pub gid: u32,
|
||||||
|
pub pid: i32,
|
||||||
|
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LinuxUser {
|
||||||
|
/// Creates a new [`LinuxUser`] from a [`UCred`].
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `ucred` - A reference to a [`UCred`] struct containing the user's credentials.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(LinuxUser)` - if the user was successfully created.
|
||||||
|
/// * `Err(anyhow::Error)` - if there was an error creating the user.
|
||||||
|
pub fn from_ucred(ucred: UCred) -> anyhow::Result<Self> {
|
||||||
|
let user =
|
||||||
|
uzers::get_user_by_uid(ucred.uid()).context("The UID of the user does not exist.")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
uid: ucred.uid(),
|
||||||
|
gid: ucred.gid(),
|
||||||
|
pid: ucred
|
||||||
|
.pid()
|
||||||
|
.ok_or(anyhow::anyhow!("The client did not have a PID."))?,
|
||||||
|
name: user.name().to_string_lossy().to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the [`LinuxUser`] of a [`Connection`].
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `connection` - A reference to a [`Connection`] struct containing the user's credentials.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(LinuxUser)` - if the user was successfully gotten.
|
||||||
|
/// * `Err(anyhow::Error)` - if there was an error getting the user.
|
||||||
|
pub fn from_connection(connection: &Connection) -> anyhow::Result<Self> {
|
||||||
|
let ucred = connection.peer_credentials();
|
||||||
|
|
||||||
|
Self::from_ucred(ucred)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "nye-package"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { version = "1.0.104", features = ["backtrace"] }
|
||||||
|
semver = { version = "1.0.28", features = ["serde"] }
|
||||||
|
serde = { version = "1.0.229", features = ["derive"] }
|
||||||
|
tempfile = "3.27.0"
|
||||||
|
tokio = { version = "1.53.1", default-features = false, features = ["fs"] }
|
||||||
|
toml = { version = "1.1.4", features = ["preserve_order"] }
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use semver::Version;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::shared::Architecture;
|
||||||
|
|
||||||
|
/// An installable package's manifest, compiled from a [`Manifest`](crate::manifest::Manifest).
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct InstallableManifest {
|
||||||
|
/// The package's name, version, and architecture.
|
||||||
|
pub package: InstallableManifestPackage,
|
||||||
|
|
||||||
|
/// The exposed artifacts of the package, such as binaries or libraries.
|
||||||
|
pub exposed: InstallableManifestExposed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The installable package's name, version, and architecture.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct InstallableManifestPackage {
|
||||||
|
/// The package's name.
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// The package's version.
|
||||||
|
pub version: Version,
|
||||||
|
|
||||||
|
/// The installable package's architecture.
|
||||||
|
pub architecture: Architecture,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The installable package's exposed artifacts, such as binaries or libraries.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct InstallableManifestExposed {
|
||||||
|
/// The installable package's exposed binaries.
|
||||||
|
#[serde(rename = "bin")]
|
||||||
|
pub binaries: Vec<InstallableManifestExposedBinary>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An installable package's exposed binary.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct InstallableManifestExposedBinary {
|
||||||
|
/// The name as it'll be exposed in the system.
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// The path to the binary, relative to the `Installable.toml` file.
|
||||||
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
//! Manifest and utilities to handle package installables.
|
||||||
|
|
||||||
|
pub mod manifest;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
//! This crate provides functionality for managing Nye packages, including bundling, installing,
|
||||||
|
//! and uninstalling them.
|
||||||
|
|
||||||
|
pub mod installable;
|
||||||
|
pub mod project;
|
||||||
|
pub mod shared;
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use semver::Version;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
use crate::shared::Architecture;
|
||||||
|
|
||||||
|
/// A Nye package manifest.
|
||||||
|
///
|
||||||
|
/// It describes the package's general information, dependencies, exposed artifacts, and other
|
||||||
|
/// things. It's used by the Nye package manager to know how to install, update, and remove the
|
||||||
|
/// package.
|
||||||
|
///
|
||||||
|
/// This is currently stored at the package's root directory in a file called `Nye.toml`. The file
|
||||||
|
/// is in TOML format, and is deserialized into this struct when the package manager needs to read
|
||||||
|
/// it.
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ProjectManifest {
|
||||||
|
/// The package's general information, such as its name, version, and description.
|
||||||
|
pub package: ProjectManifestPackage,
|
||||||
|
|
||||||
|
/// The package's exposed binaries, libraries, and other artifacts.
|
||||||
|
pub exposed: ProjectManifestExposed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectManifest {
|
||||||
|
/// Validates the manifest, ensuring that the values are well-formed and consistent.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(())` - When the manifest is valid.
|
||||||
|
/// * `Err(anyhow::Error)` - When the manifest is invalid, with a message describing the issue.
|
||||||
|
pub fn validate(&self) -> anyhow::Result<()> {
|
||||||
|
if self.package.architectures.is_empty() {
|
||||||
|
anyhow::bail!("The package must support at least one architecture.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, architecture) in self.package.architectures.iter().enumerate() {
|
||||||
|
if self
|
||||||
|
.package
|
||||||
|
.architectures
|
||||||
|
.iter()
|
||||||
|
.position(|a| a == architecture)
|
||||||
|
!= Some(i)
|
||||||
|
{
|
||||||
|
anyhow::bail!(
|
||||||
|
"The package's architectures must be unique, yet {:?} appears more than once.",
|
||||||
|
architecture
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Nye package's general information, such as its name, version, and description.
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ProjectManifestPackage {
|
||||||
|
/// The package's name, which is used to identify it in the Nye package manager.
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// The package's version, which is used to identify it in the Nye package manager.
|
||||||
|
pub version: Version,
|
||||||
|
|
||||||
|
/// The package's description, which is used to describe it in the Nye package manager.
|
||||||
|
pub description: Option<String>,
|
||||||
|
|
||||||
|
/// The package's supported architectures, which is used to determine if the package can be
|
||||||
|
/// installed on the current system.
|
||||||
|
pub architectures: Vec<Architecture>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Nye package's exposed binaries, libraries, and other artifacts.
|
||||||
|
///
|
||||||
|
/// Only the artifacts listed here are exposed to the user and dependants.
|
||||||
|
#[derive(Default, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ProjectManifestExposed {
|
||||||
|
/// The package's exposed binaries, which are the executables that the package provides to the
|
||||||
|
/// user.
|
||||||
|
#[serde(rename = "bin")]
|
||||||
|
pub binaries: Vec<ProjectManifestExposeBinary>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Nye package's exposed binary, which is an executable that the package provides to the user.
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ProjectManifestExposeBinary {
|
||||||
|
/// The name under which the binary is exposed.
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// The path to the binary, relative to the `./src/{arch}/bin` directory (which is itself
|
||||||
|
/// relative to the `Nye.toml` file).
|
||||||
|
///
|
||||||
|
/// If missing, it is assumed that the binary is located at `./src/{arch}/bin/{name}`.
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// The architectures for which the binary is available.
|
||||||
|
///
|
||||||
|
/// If not specified, it is assumed that the binary is available for all architectures
|
||||||
|
/// specified in the `package.architectures` field.
|
||||||
|
pub architectures: Option<Vec<Architecture>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a Nye package's manifest from the given path and returns it as a [`Manifest`] struct.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `path` - The path to the manifest file. This is usually the `Nye.toml` file at the root of
|
||||||
|
/// the package's directory.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(Manifest)` - The manifest struct if the file was read and parsed successfully.
|
||||||
|
/// * `Err(anyhow::Error)` - An error if the file could not be read or parsed.
|
||||||
|
pub async fn read(path: impl AsRef<Path>) -> anyhow::Result<ProjectManifest> {
|
||||||
|
let string = fs::read_to_string(path)
|
||||||
|
.await
|
||||||
|
.context("Could not read package's manifest.")?;
|
||||||
|
|
||||||
|
parse(&string)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a Nye package's manifest from the given string and returns it as a [`Manifest`] struct.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `string` - The string containing the manifest in TOML format.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(Manifest)` - The manifest struct if the string was parsed successfully.
|
||||||
|
/// * `Err(anyhow::Error)` - An error if the string could not be parsed.
|
||||||
|
pub fn parse(string: &str) -> anyhow::Result<ProjectManifest> {
|
||||||
|
let manifest: ProjectManifest =
|
||||||
|
toml::from_str(string).context("Could not parse the package's manifest.")?;
|
||||||
|
|
||||||
|
Ok(manifest)
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
//! Manifest and utilities to handle package projects.
|
||||||
|
//!
|
||||||
|
//! ## Package Project Structure
|
||||||
|
//!
|
||||||
|
//! A Nye package project is a project that contains the files to bundle into a Nye package,
|
||||||
|
//! together with a `Nye.toml` manifest file that describes the package's metadata and
|
||||||
|
//! configuration.
|
||||||
|
//!
|
||||||
|
//! A package project's structure looks like follows:
|
||||||
|
//!
|
||||||
|
//! ```text
|
||||||
|
//! package/ # The root directory of the package project
|
||||||
|
//! Nye.toml # The package's manifest file
|
||||||
|
//! shared/ # A directory containing files that are shared across all architectures of the package.
|
||||||
|
//! bin/ # A directory containing the package's binaries that are shared across all architectures.
|
||||||
|
//! {architecture}/ # A directory for each architecture that the package supports, containing the files to bundle for that architecture.
|
||||||
|
//! bin/ # A directory containing the package's binaries for that architecture.
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! The architectures supported are those returnable by [`std::env::consts::ARCH`], such as
|
||||||
|
//! `x86_64`, `aarch64`, and `arm`. The architecture-specific directories must match the names of
|
||||||
|
//! the architectures specified in the `package.architectures` field of the `Nye.toml` manifest
|
||||||
|
//! file.
|
||||||
|
//!
|
||||||
|
//! When an artifact is not available for a specific architecture, it'll fall back to the shared
|
||||||
|
//! version of the artifact if it exists.
|
||||||
|
//!
|
||||||
|
//! ### Package Project's Manifest File
|
||||||
|
//!
|
||||||
|
//! The `Nye.toml` manifest file is a TOML file at the package project's root directory that
|
||||||
|
//! describes the package's metadata and configuration.
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! [package]
|
||||||
|
//! name = "my-package" # The package's name
|
||||||
|
//! version = "1.0.0" # The package's version
|
||||||
|
//! architectures = ["x86_64", "aarch64"] # The architectures that the package supports
|
||||||
|
//!
|
||||||
|
//! [[exposed.bin]]
|
||||||
|
//! name = "my-binary" # The name of the binary as it'll be exposed in the system
|
||||||
|
//! path = "my-binary" # The path to the binary inside each `bin/` directory
|
||||||
|
//! architectures = ["x86_64", "aarch64"] # The architectures that this binary is available for
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! The supported fields are the following:
|
||||||
|
//!
|
||||||
|
//! - `package.name` is the name of the package. It'll be used by (future) repositories to identify
|
||||||
|
//! the package.
|
||||||
|
//! - `package.version` is the semver version of the package. It'll be used by (future)
|
||||||
|
//! repositories and dependencies to identify the package's version.
|
||||||
|
//! - `package.architectures` is an array of the architectures that the package supports.
|
||||||
|
//!
|
||||||
|
//! - `exposed.bin` is an array of the binaries that the package exposes. Not all binaries
|
||||||
|
//! available in a package are exposed to the user and dependants. Only the binaries listed in
|
||||||
|
//! this array will be added to the `PATH` and made available to the user.
|
||||||
|
//! - `exposed.bin.name` is the name of the binary as it'll be exposed in the system. This is the
|
||||||
|
//! name that the user and dependants will use to invoke the binary.
|
||||||
|
//! - `exposed.bin.path` is the path to the binary inside each `bin/` directory, or under
|
||||||
|
//! `shared/bin/` if the binary is not available for an architecture. When not specified, it'll
|
||||||
|
//! default to the binary's name.
|
||||||
|
//! - `exposed.bin.architectures` is an array of the architectures that this binary is exposed
|
||||||
|
//! for.
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
use crate::installable::manifest::InstallableManifestPackage;
|
||||||
|
use crate::shared::Architecture;
|
||||||
|
|
||||||
|
pub mod manifest;
|
||||||
|
|
||||||
|
/// Bundles a package project into a Nye package installable.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `input` - The path to the package project directory. It must contain a `Nye.toml` manifest
|
||||||
|
/// file and the files to bundle into the package.
|
||||||
|
/// * `output` - The path to the output directory where the bundled package will be created.
|
||||||
|
/// * `arch` - The architecture to bundle the package for. It must be one of the architectures
|
||||||
|
/// specified in the `package.architectures` field of the `Nye.toml` manifest file.
|
||||||
|
pub async fn bundle(input: impl AsRef<Path>, architecture: Architecture) -> anyhow::Result<()> {
|
||||||
|
let input = input.as_ref();
|
||||||
|
|
||||||
|
let manifest = manifest::read(input.join("Nye.toml"))
|
||||||
|
.await
|
||||||
|
.context("Could not read the manifest of the project to bundle.")?;
|
||||||
|
|
||||||
|
manifest.validate()?;
|
||||||
|
|
||||||
|
if !manifest.package.architectures.contains(&architecture) {
|
||||||
|
anyhow::bail!(
|
||||||
|
concat!(
|
||||||
|
"The architecture `{:?}` is not supported by the package project. The supported ",
|
||||||
|
"architectures are: {:?}.",
|
||||||
|
),
|
||||||
|
architecture,
|
||||||
|
manifest.package.architectures
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_arch = input.join(architecture.to_string());
|
||||||
|
let source_shared = input.join("shared");
|
||||||
|
|
||||||
|
let installable_manifest_package = InstallableManifestPackage {
|
||||||
|
name: manifest.package.name,
|
||||||
|
version: manifest.package.version,
|
||||||
|
architecture,
|
||||||
|
};
|
||||||
|
|
||||||
|
let source_arch_bin = source_arch.join("bin");
|
||||||
|
let source_shared_bin = source_shared.join("bin");
|
||||||
|
|
||||||
|
let mut installable_manifest_exposed_bins = Vec::new();
|
||||||
|
|
||||||
|
let working_dir = tempfile::tempdir()
|
||||||
|
.context("Could not create temporary working directory to bundle project.")?;
|
||||||
|
let working_dir_path = working_dir.path();
|
||||||
|
|
||||||
|
for binary in manifest.exposed.binaries {
|
||||||
|
if let Some(architectures) = binary.architectures {
|
||||||
|
if !architectures.contains(&architecture) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = binary.path.as_ref().unwrap_or(&PathBuf::from(&binary.name));
|
||||||
|
|
||||||
|
let path_in_arch = source_arch_bin.join(&path);
|
||||||
|
let path_in_shared = source_shared_bin.join(&path);
|
||||||
|
|
||||||
|
let path_in_arch_exists = fs::try_exists(path)
|
||||||
|
.await
|
||||||
|
.context("Could not check whether binary existed")?;
|
||||||
|
let path_in_shared_exists = fs::try_exists(path)
|
||||||
|
.await
|
||||||
|
.context("Could not check whether binary existed")?;
|
||||||
|
|
||||||
|
if path_in_arch_exists {
|
||||||
|
fs::copy(from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A CPU architecture that a Nye package can support.
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum Architecture {
|
||||||
|
X86,
|
||||||
|
X86_64,
|
||||||
|
Arm,
|
||||||
|
Aarch64,
|
||||||
|
M68k,
|
||||||
|
Mips,
|
||||||
|
Mips32r6,
|
||||||
|
Mips64,
|
||||||
|
Mips64r6,
|
||||||
|
Csky,
|
||||||
|
Powerpc,
|
||||||
|
Powerpc64,
|
||||||
|
Riscv32,
|
||||||
|
Riscv64,
|
||||||
|
S390x,
|
||||||
|
Sparc,
|
||||||
|
Sparc64,
|
||||||
|
Hexagon,
|
||||||
|
Loongarch32,
|
||||||
|
Loongarch64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Architecture {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Architecture::X86 => write!(f, "x86"),
|
||||||
|
Architecture::X86_64 => write!(f, "x86_64"),
|
||||||
|
Architecture::Arm => write!(f, "arm"),
|
||||||
|
Architecture::Aarch64 => write!(f, "aarch64"),
|
||||||
|
Architecture::M68k => write!(f, "m68k"),
|
||||||
|
Architecture::Mips => write!(f, "mips"),
|
||||||
|
Architecture::Mips32r6 => write!(f, "mips32r6"),
|
||||||
|
Architecture::Mips64 => write!(f, "mips64"),
|
||||||
|
Architecture::Mips64r6 => write!(f, "mips64r6"),
|
||||||
|
Architecture::Csky => write!(f, "csky"),
|
||||||
|
Architecture::Powerpc => write!(f, "powerpc"),
|
||||||
|
Architecture::Powerpc64 => write!(f, "powerpc64"),
|
||||||
|
Architecture::Riscv32 => write!(f, "riscv32"),
|
||||||
|
Architecture::Riscv64 => write!(f, "riscv64"),
|
||||||
|
Architecture::S390x => write!(f, "s390x"),
|
||||||
|
Architecture::Sparc => write!(f, "sparc"),
|
||||||
|
Architecture::Sparc64 => write!(f, "sparc64"),
|
||||||
|
Architecture::Hexagon => write!(f, "hexagon"),
|
||||||
|
Architecture::Loongarch32 => write!(f, "loongarch32"),
|
||||||
|
Architecture::Loongarch64 => write!(f, "loongarch64"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "test-package"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A package for testing the Nye package manager."
|
||||||
|
architectures = ["x86", "x86_64"]
|
||||||
|
|
||||||
|
[[expose.bin]]
|
||||||
|
name = "shared-binary"
|
||||||
|
|
||||||
|
[[expose.bin]]
|
||||||
|
name = "exposed-binary"
|
||||||
|
|
||||||
|
[[expose.bin]]
|
||||||
|
name = "exposed-binary-x86"
|
||||||
|
architectures = ["x86"]
|
||||||
|
|
||||||
|
[[expose.bin]]
|
||||||
|
name = "exposed-binary-x86_64"
|
||||||
|
architectures = ["x86_64"]
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
echo "This binary is shared across all architectures."
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
echo "Hello from the test Nye package for the x86 architecture!"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
echo "Hello from the test Nye package for the x86 architecture! This binary's name is arch-specific."
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
echo "This binary for the x86 architecture is not exposed."
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
echo "Hello from the test Nye package for the x86_64 architecture!"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
echo "Hello from the test Nye package for the x86_64 architecture! This binary's name is arch-specific."
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
echo "This binary for the x86_64 architecture is not exposed."
|
||||||
Generated
+554
@@ -0,0 +1,554 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.104"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
"terminal_size",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dotenvy"
|
||||||
|
version = "0.15.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.103"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"futures-util",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.189"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libredox"
|
||||||
|
version = "0.1.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nye"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"clap",
|
||||||
|
"dotenvy",
|
||||||
|
"tokio",
|
||||||
|
"whoami",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-foundation"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-system-configuration"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396"
|
||||||
|
dependencies = [
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||||
|
dependencies = [
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "terminal_size"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
|
||||||
|
dependencies = [
|
||||||
|
"rustix",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.126"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.126"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.126"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.126"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.103"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "whoami"
|
||||||
|
version = "2.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libredox",
|
||||||
|
"objc2-system-configuration",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "nye-terminal"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { version = "1.0.104", features = ["backtrace"] }
|
||||||
|
clap = { version = "4.6.5", features = ["derive", "env", "wrap_help"] }
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
tokio = { version = "1.53.1", features = ["full"] }
|
||||||
|
whoami = { version = "2.1.2", default-features = false, features = ["std"] }
|
||||||
|
nye-wire-protocol = { version = "0.1.0", path = "../nye-wire-protocol" }
|
||||||
|
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||||
|
nye-console = { version = "0.1.0", path = "../nye-console" }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nye"
|
||||||
|
path = "src/main.rs"
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
//! CLI arguments for the terminal application.
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Debug, clap::Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
/// Whether to display logs on the terminal.
|
||||||
|
#[arg(short, long, global = true, default_value_t = false)]
|
||||||
|
pub debug: bool,
|
||||||
|
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, clap::Subcommand)]
|
||||||
|
pub enum Command {
|
||||||
|
/// Sets up the package manager for the current user or the system.
|
||||||
|
#[command(visible_alias = "s")]
|
||||||
|
Setup(SetupCommand),
|
||||||
|
|
||||||
|
/// Bundle the current project into a single package.
|
||||||
|
#[command(visible_alias = "b")]
|
||||||
|
Bundle(BundleCommand),
|
||||||
|
|
||||||
|
/// Install one or more packages.
|
||||||
|
#[command(visible_alias = "i")]
|
||||||
|
Install(InstallCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, clap::Args)]
|
||||||
|
pub struct SetupCommand;
|
||||||
|
|
||||||
|
#[derive(Debug, clap::Args)]
|
||||||
|
pub struct BundleCommand {
|
||||||
|
#[arg(short, long, default_value = ".")]
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, clap::Args)]
|
||||||
|
pub struct InstallCommand {
|
||||||
|
/// The names of the packages to install.
|
||||||
|
packages: Vec<String>,
|
||||||
|
|
||||||
|
/// The path to the directory where the package is located.
|
||||||
|
#[arg(short, long)]
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
use nye_wire_protocol::Connection;
|
||||||
|
|
||||||
|
use crate::args::BundleCommand;
|
||||||
|
|
||||||
|
pub async fn run(_command: &BundleCommand, connection: &Connection) -> anyhow::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
use nye_wire_protocol::Connection;
|
||||||
|
|
||||||
|
use crate::args::InstallCommand;
|
||||||
|
|
||||||
|
pub async fn run(_command: &InstallCommand, connection: &Connection) -> anyhow::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod bundle;
|
||||||
|
pub mod install;
|
||||||
|
pub mod setup;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use anyhow::Context;
|
||||||
|
use nye_wire_protocol::Connection;
|
||||||
|
use nye_wire_protocol::messages::MessageRequest;
|
||||||
|
|
||||||
|
use crate::args::SetupCommand;
|
||||||
|
|
||||||
|
pub async fn run(_command: &SetupCommand, connection: &Connection) -> anyhow::Result<()> {
|
||||||
|
let progress = nye_console::spinner(format!(
|
||||||
|
"Setting up the Nye package manager for {}...",
|
||||||
|
whoami::username().context("Could not get current user's username.")?
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut stream = connection
|
||||||
|
.send_stream()
|
||||||
|
.await
|
||||||
|
.context("Could not open a stream to the daemon.")?;
|
||||||
|
|
||||||
|
stream
|
||||||
|
.send(MessageRequest::Setup)
|
||||||
|
.await
|
||||||
|
.context("Could not send setup request to the daemon.")?;
|
||||||
|
|
||||||
|
let _response = stream
|
||||||
|
.recv_response()
|
||||||
|
.await
|
||||||
|
.context("Could not receive response from the daemon.")?;
|
||||||
|
|
||||||
|
progress.finish_with_message("Done! The Nye package manager was setup for your user.");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
use crate::args::{Args, Command};
|
||||||
|
|
||||||
|
mod args;
|
||||||
|
mod commands;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
dotenvy::dotenv().ok();
|
||||||
|
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
if args.debug {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(EnvFilter::new(
|
||||||
|
"off,nye_terminal=trace,nye_wire_protocol=trace",
|
||||||
|
))
|
||||||
|
.with_target(false)
|
||||||
|
.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
let connection = nye_wire_protocol::connect("/run/nye-packages.sock").await?;
|
||||||
|
|
||||||
|
match &args.command {
|
||||||
|
Command::Setup(command) => {
|
||||||
|
commands::setup::run(command, &connection).await?;
|
||||||
|
}
|
||||||
|
Command::Bundle(command) => {
|
||||||
|
commands::bundle::run(command, &connection).await?;
|
||||||
|
}
|
||||||
|
Command::Install(command) => {
|
||||||
|
commands::install::run(command, &connection).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "nye-wire-protocol"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { version = "1.0.104", features = ["backtrace"] }
|
||||||
|
bitcode = "0.6.9"
|
||||||
|
papaya = "0.2.4"
|
||||||
|
rustix = { version = "1.1.4", features = ["linux_latest", "net", "system"] }
|
||||||
|
tokio = { version = "1.53.1", features = ["io-util", "net"] }
|
||||||
|
tracing = { version = "0.1.44", features = ["async-await"] }
|
||||||
@@ -0,0 +1,550 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use papaya::HashMap;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio::net::unix::{OwnedReadHalf, OwnedWriteHalf, UCred};
|
||||||
|
use tokio::net::{UnixListener, UnixStream};
|
||||||
|
use tokio::sync::{Notify, mpsc};
|
||||||
|
|
||||||
|
use crate::messages::{Message, MessageRequest, MessageResponse, StreamMessage};
|
||||||
|
|
||||||
|
pub mod messages;
|
||||||
|
|
||||||
|
/// Binds a new [`UnixListener`] to the specified path.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `path` - The path to bind the UnixListener to.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(Self)` - If the binding was successful, returns a new instance of [`Listener`].
|
||||||
|
/// * `Err(anyhow::Error)` - If the binding failed, returns an error.
|
||||||
|
pub async fn listen(path: impl AsRef<Path>) -> anyhow::Result<Listener> {
|
||||||
|
let listener = UnixListener::bind(path.as_ref()).context(format!(
|
||||||
|
"Could not listen for incoming connections at {}.",
|
||||||
|
path.as_ref().display()
|
||||||
|
))?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"Listening for incoming connections at {}",
|
||||||
|
path.as_ref().display()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Listener { listener })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connects to a Unix socket at the specified path and returns a new instance of [`Connection`].
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `path` - The path to the Unix socket to connect to.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(Connection)` - If the connection was successful, returns a new instance of
|
||||||
|
/// [`Connection`].
|
||||||
|
/// * `Err(anyhow::Error)` - If the connection failed, returns an error.
|
||||||
|
pub async fn connect(path: impl AsRef<Path>) -> anyhow::Result<Connection> {
|
||||||
|
let stream = UnixStream::connect(path.as_ref()).await.context(format!(
|
||||||
|
"Could not connect to the Unix socket at {}.",
|
||||||
|
path.as_ref().display()
|
||||||
|
))?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"Connected to the Unix socket at {}",
|
||||||
|
path.as_ref().display()
|
||||||
|
);
|
||||||
|
|
||||||
|
Connection::new(stream, PartyType::Client)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wrapper around a [`UnixListener`] to accept incoming connections on a Unix socket.
|
||||||
|
pub struct Listener {
|
||||||
|
listener: UnixListener,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener {
|
||||||
|
/// Accepts an incoming connection on the Unix socket.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(Connection)` - If the connection was accepted successfully, returns a new instance of
|
||||||
|
/// [`Connection`].
|
||||||
|
/// * `Err(anyhow::Error)` - If the connection could not be accepted, returns an error.
|
||||||
|
pub async fn accept(&self) -> anyhow::Result<Connection> {
|
||||||
|
let (stream, _) = self.listener.accept().await?;
|
||||||
|
|
||||||
|
Connection::new(stream, PartyType::Server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An enum representing the type of party (client or server) in a connection.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum PartyType {
|
||||||
|
Client,
|
||||||
|
Server,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartyType {
|
||||||
|
/// Creates a new instance of [`PartyType`] from a given ID.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `id` - The ID to derive the party type from.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`PartyType`] - A new instance of [`PartyType`] derived from the given ID.
|
||||||
|
fn from_id(id: u64) -> Self {
|
||||||
|
// The most significant bit of the ID indicates the party type.
|
||||||
|
|
||||||
|
if id & (1 << 63) != 0 {
|
||||||
|
PartyType::Server
|
||||||
|
} else {
|
||||||
|
PartyType::Client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a given ID to a party-specific ID based on the party type.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `id` - The ID to convert to a party-specific ID.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`u64`] - The party-specific ID derived from the given ID.
|
||||||
|
fn type_id(&self, id: u64) -> u64 {
|
||||||
|
match self {
|
||||||
|
PartyType::Client => id & !(1 << 63),
|
||||||
|
PartyType::Server => id | (1 << 63),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamsMap = Arc<HashMap<u64, mpsc::UnboundedSender<Message>>>;
|
||||||
|
|
||||||
|
type PendingStreamsTx = mpsc::UnboundedSender<Stream>;
|
||||||
|
type PendingStreamsRx = mpsc::UnboundedReceiver<Stream>;
|
||||||
|
|
||||||
|
/// A wrapper around a [`UnixStream`] to represent a connection to a Unix socket.
|
||||||
|
pub struct Connection {
|
||||||
|
/// The type of party (client or server) of the current party.
|
||||||
|
party_type: PartyType,
|
||||||
|
|
||||||
|
/// A shared map of protocol streams to route incoming messages to.
|
||||||
|
streams: StreamsMap,
|
||||||
|
|
||||||
|
/// The peer credentials of the other party in the connection.
|
||||||
|
peer_credentials: UCred,
|
||||||
|
|
||||||
|
/// An atomic counter to generate unique stream IDs for each protocol stream.
|
||||||
|
stream_id_counter: AtomicU64,
|
||||||
|
|
||||||
|
/// Notifies when the connection is closing, allowing tasks to clean up resources.
|
||||||
|
closing: Arc<Notify>,
|
||||||
|
|
||||||
|
/// A channel to receive pending streams that need to be processed by this party.
|
||||||
|
pending_streams: PendingStreamsRx,
|
||||||
|
|
||||||
|
/// A channel to send outgoing messages to the other party.
|
||||||
|
outgoing_tx: mpsc::UnboundedSender<StreamMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
/// Creates a new instance of [`Connection`] from an existing [`UnixStream`].
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `party_type` - The type of party (client or server) that this connection represents.
|
||||||
|
/// * `stream` - The existing [`UnixStream`] to wrap in a [`Connection`].
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Self` - A new instance of [`Connection`] wrapping the provided [`UnixStream`].
|
||||||
|
fn new(stream: UnixStream, party_type: PartyType) -> anyhow::Result<Self> {
|
||||||
|
let peer_credentials = stream
|
||||||
|
.peer_cred()
|
||||||
|
.context("Could not get the credentials of the connection's peer.")?;
|
||||||
|
|
||||||
|
let (stream_rx, stream_tx) = stream.into_split();
|
||||||
|
|
||||||
|
let streams: StreamsMap = Default::default();
|
||||||
|
|
||||||
|
let (outgoing_tx, outgoing_rx) = mpsc::unbounded_channel();
|
||||||
|
let (pending_streams_tx, pending_streams_rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
let closing = Arc::new(Notify::new());
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let stream_rx = stream_rx;
|
||||||
|
let streams = streams.clone();
|
||||||
|
let pending_streams_tx = pending_streams_tx.clone();
|
||||||
|
let outgoing_tx = outgoing_tx.clone();
|
||||||
|
let closing = closing.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let result = Self::route_incoming(
|
||||||
|
stream_rx,
|
||||||
|
streams,
|
||||||
|
pending_streams_tx,
|
||||||
|
party_type,
|
||||||
|
outgoing_tx,
|
||||||
|
closing,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(e) = &result {
|
||||||
|
tracing::warn!("{e:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let result = Self::route_outgoing(stream_tx, outgoing_rx).await;
|
||||||
|
|
||||||
|
if let Err(e) = &result {
|
||||||
|
tracing::warn!("{e:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
party_type,
|
||||||
|
peer_credentials,
|
||||||
|
streams,
|
||||||
|
stream_id_counter: AtomicU64::new(0),
|
||||||
|
closing,
|
||||||
|
pending_streams: pending_streams_rx,
|
||||||
|
outgoing_tx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives a pending stream that needs to be processed by this party.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(Stream)` - If a pending stream was received successfully, returns the stream.
|
||||||
|
/// * `Err(anyhow::Error)` - If the pending streams channel was closed, returns an error.
|
||||||
|
pub async fn recv_stream(&mut self) -> anyhow::Result<Stream> {
|
||||||
|
let stream = self
|
||||||
|
.pending_streams
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
.context("The pending streams channel was closed, which should not happen.")?;
|
||||||
|
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new protocol stream with a unique ID and returns it.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(Stream)` - If the stream was created successfully, returns the new stream.
|
||||||
|
/// * `Err(anyhow::Error)` - If an error occurred while creating the stream, returns an error.
|
||||||
|
pub async fn send_stream(&self) -> anyhow::Result<Stream> {
|
||||||
|
let id = self.stream_id_counter.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let id = self.party_type.type_id(id);
|
||||||
|
|
||||||
|
let (incoming_tx, incoming_rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
let stream = Stream::new(
|
||||||
|
id,
|
||||||
|
self.streams.clone(),
|
||||||
|
incoming_tx,
|
||||||
|
incoming_rx,
|
||||||
|
self.outgoing_tx.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.streams.pin().insert(id, stream.incoming_tx.clone());
|
||||||
|
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Routes incoming messages from the Unix stream to the appropriate protocol streams.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `stream_rx` - The read half of the Unix stream to read incoming messages from.
|
||||||
|
/// * `streams` - A shared map of protocol streams to route incoming messages to.
|
||||||
|
/// * `pending` - A channel to send pending streams that need to be processed by this party.
|
||||||
|
/// * `party_type` - The party type of the current party.
|
||||||
|
/// * `outgoing_tx` - A channel to send outgoing messages to the other party.
|
||||||
|
/// * `closing` - A notification to signal when the connection is closing.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `!` - This function runs indefinitely and does not return.
|
||||||
|
/// * `Err(anyhow::Error)` - If an error occurs while routing incoming messages, returns an
|
||||||
|
/// error.
|
||||||
|
async fn route_incoming(
|
||||||
|
mut stream_rx: OwnedReadHalf,
|
||||||
|
streams: StreamsMap,
|
||||||
|
pending: PendingStreamsTx,
|
||||||
|
party_type: PartyType,
|
||||||
|
outgoing_tx: mpsc::UnboundedSender<StreamMessage>,
|
||||||
|
closing: Arc<Notify>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
loop {
|
||||||
|
// Only close on message boundaries.
|
||||||
|
let message_size = tokio::select! {
|
||||||
|
_ = closing.notified() => {
|
||||||
|
// The connection is closing, so we should stop routing incoming messages and
|
||||||
|
// return.
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
size = stream_rx.read_u32() => {
|
||||||
|
size.context("Could not read the size of the incoming message.")?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut message_data = vec![0u8; message_size as usize];
|
||||||
|
stream_rx
|
||||||
|
.read_exact(&mut message_data)
|
||||||
|
.await
|
||||||
|
.context("Could not read the data of the incoming message.")?;
|
||||||
|
|
||||||
|
let message: StreamMessage =
|
||||||
|
bitcode::decode(&message_data).context("Could not decode the incoming message.")?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
stream.id = message.stream_id,
|
||||||
|
message.kind = ?message.data.kind(),
|
||||||
|
"Received a message"
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(stream) = streams.pin().get(&message.stream_id) {
|
||||||
|
tracing::trace!(
|
||||||
|
stream.id = message.stream_id,
|
||||||
|
message.kind = ?message.data.kind(),
|
||||||
|
"Routing the message to the existing stream"
|
||||||
|
);
|
||||||
|
|
||||||
|
stream.send(message.data).context(
|
||||||
|
"Could not send the message through the stream's incoming messages channel.",
|
||||||
|
)?;
|
||||||
|
} else if PartyType::from_id(message.stream_id) != party_type {
|
||||||
|
// The other party initiated this stream, so we need to create a new stream and
|
||||||
|
// send it to the pending channel.
|
||||||
|
|
||||||
|
tracing::trace!(
|
||||||
|
stream.id = message.stream_id,
|
||||||
|
message.kind = ?message.data.kind(),
|
||||||
|
"Creating a new stream for the incoming message"
|
||||||
|
);
|
||||||
|
|
||||||
|
let (incoming_tx, incoming_rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
let stream = Stream::new(
|
||||||
|
message.stream_id,
|
||||||
|
streams.clone(),
|
||||||
|
incoming_tx.clone(),
|
||||||
|
incoming_rx,
|
||||||
|
outgoing_tx.clone(),
|
||||||
|
);
|
||||||
|
stream.incoming_tx.send(message.data).context(concat!(
|
||||||
|
"Could not send the first message of the stream through the incoming ",
|
||||||
|
"messages channel."
|
||||||
|
))?;
|
||||||
|
|
||||||
|
streams.pin().insert(message.stream_id, incoming_tx);
|
||||||
|
pending
|
||||||
|
.send(stream)
|
||||||
|
.context("Could not send the stream through the pending streams channel.")?;
|
||||||
|
} else {
|
||||||
|
anyhow::bail!(concat!(
|
||||||
|
"A message was received from the other party for a stream that does not ",
|
||||||
|
"exist. The ID said we created the stream, yet we did not or it was already ",
|
||||||
|
"closed. This is a bug, either on this side or the other side (daemon or ",
|
||||||
|
"client)."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Routes outgoing messages from the protocol streams to the Unix stream.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `stream_tx` - The write half of the Unix stream to write outgoing messages to.
|
||||||
|
/// * `outgoing_rx` - A channel to receive outgoing messages from the protocol streams.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `!` - This function runs indefinitely and does not return.
|
||||||
|
/// * `Err(anyhow::Error)` - If an error occurs while routing outgoing messages, returns an
|
||||||
|
/// error.
|
||||||
|
async fn route_outgoing(
|
||||||
|
mut stream_tx: OwnedWriteHalf,
|
||||||
|
mut outgoing_rx: mpsc::UnboundedReceiver<StreamMessage>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
loop {
|
||||||
|
// If closed, the connection was dropped.
|
||||||
|
let Some(message) = outgoing_rx.recv().await else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let message_data = bitcode::encode(&message);
|
||||||
|
let message_size = message_data.len() as u32;
|
||||||
|
|
||||||
|
stream_tx
|
||||||
|
.write_u32(message_size)
|
||||||
|
.await
|
||||||
|
.context("Could not send outgoing message's size through the socket.")?;
|
||||||
|
stream_tx
|
||||||
|
.write_all(&message_data)
|
||||||
|
.await
|
||||||
|
.context("Could not send outgoing message's data through the socket.")?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
stream.id = message.stream_id,
|
||||||
|
message.kind = ?message.data.kind(),
|
||||||
|
"Sent a message"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the peer credentials of the other party in the connection.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`UCred`] - A reference to the peer credentials of the other party in the connection.
|
||||||
|
pub fn peer_credentials(&self) -> UCred {
|
||||||
|
self.peer_credentials
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Connection {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let peer_credentials = self.peer_credentials();
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
user.uid = peer_credentials.uid(),
|
||||||
|
user.gid = peer_credentials.gid(),
|
||||||
|
user.pid = peer_credentials.pid(),
|
||||||
|
"Closing the connection"
|
||||||
|
);
|
||||||
|
|
||||||
|
self.closing.notify_waiters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A representation of a protocol stream that can send and receive messages over a connection.
|
||||||
|
pub struct Stream {
|
||||||
|
id: u64,
|
||||||
|
|
||||||
|
streams: StreamsMap,
|
||||||
|
|
||||||
|
incoming_tx: mpsc::UnboundedSender<Message>,
|
||||||
|
incoming_rx: mpsc::UnboundedReceiver<Message>,
|
||||||
|
|
||||||
|
outgoing_tx: mpsc::UnboundedSender<StreamMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream {
|
||||||
|
/// Creates a new instance of [`Stream`] with the specified ID.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `id` - The ID to assign to the new stream.
|
||||||
|
/// * `streams` - A shared map of protocol streams to route incoming messages to.
|
||||||
|
/// * `incoming_rx` - A channel to receive incoming messages from the other party.
|
||||||
|
/// * `incoming_tx` - A channel to receive incoming messages from the other party.
|
||||||
|
/// * `outgoing_tx` - A channel to send outgoing messages to the other party.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`Stream`] - A new instance of [`Stream`] with the specified ID.
|
||||||
|
fn new(
|
||||||
|
id: u64,
|
||||||
|
streams: StreamsMap,
|
||||||
|
incoming_tx: mpsc::UnboundedSender<Message>,
|
||||||
|
incoming_rx: mpsc::UnboundedReceiver<Message>,
|
||||||
|
outgoing_tx: mpsc::UnboundedSender<StreamMessage>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
streams,
|
||||||
|
incoming_tx,
|
||||||
|
incoming_rx,
|
||||||
|
outgoing_tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a message over the stream to the other party.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `message` - The message to send over the stream.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(())` - If the message was sent successfully.
|
||||||
|
/// * `Err(anyhow::Error)` - If an error occurred while sending the message.
|
||||||
|
pub async fn send(&self, message: impl Into<Message>) -> anyhow::Result<()> {
|
||||||
|
let stream_message = StreamMessage {
|
||||||
|
stream_id: self.id,
|
||||||
|
data: message.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.outgoing_tx
|
||||||
|
.send(stream_message)
|
||||||
|
.context("The outgoing messages channel was closed.")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives a message from the stream sent by the other party.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(Message)` - If a message was received successfully.
|
||||||
|
/// * `Err(anyhow::Error)` - If an error occurred while receiving the message.
|
||||||
|
pub async fn recv(&mut self) -> anyhow::Result<Message> {
|
||||||
|
let message = self.incoming_rx.recv().await.context(
|
||||||
|
"The incoming messages channel for this stream was closed, which should not happen.",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives a request message from the stream sent by the other party.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(MessageRequest)` - If a request message was received successfully.
|
||||||
|
/// * `Err(anyhow::Error)` - If an error occurred while receiving the request message or the
|
||||||
|
/// message received was a response.
|
||||||
|
pub async fn recv_request(&mut self) -> anyhow::Result<MessageRequest> {
|
||||||
|
let message = self.recv().await?;
|
||||||
|
|
||||||
|
match message {
|
||||||
|
Message::Request(request) => Ok(request),
|
||||||
|
_ => anyhow::bail!(concat!(
|
||||||
|
"The message received from the other party was not a request. ",
|
||||||
|
"This is a bug, either on this side or the other side (daemon or client)."
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives a response message from the stream sent by the other party.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * `Ok(MessageResponse)` - If a response message was received successfully.
|
||||||
|
/// * `Err(anyhow::Error)` - If an error occurred while receiving the response message or the
|
||||||
|
/// message received was a request.
|
||||||
|
pub async fn recv_response(&mut self) -> anyhow::Result<MessageResponse> {
|
||||||
|
let message = self.recv().await?;
|
||||||
|
|
||||||
|
match message {
|
||||||
|
Message::Response(response) => Ok(response),
|
||||||
|
_ => anyhow::bail!(concat!(
|
||||||
|
"The message received from the other party was not a response. ",
|
||||||
|
"This is a bug, either on this side or the other side (daemon or client)."
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the ID of the stream.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`u64`] - The ID of the stream.
|
||||||
|
pub fn id(&self) -> u64 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Stream {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let id = self.id;
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
stream.id = id,
|
||||||
|
"Closing the stream and removing it from the streams map."
|
||||||
|
);
|
||||||
|
|
||||||
|
self.streams.pin().remove(&id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
use bitcode::{Decode, Encode};
|
||||||
|
|
||||||
|
/// Represents a message in the Nye wire protocol.
|
||||||
|
#[derive(Debug, Clone, Encode, Decode)]
|
||||||
|
pub struct StreamMessage {
|
||||||
|
/// The unique identifier of the stream, used to correlate requests and responses.
|
||||||
|
pub stream_id: u64,
|
||||||
|
|
||||||
|
/// The type of the message, represented as a string.
|
||||||
|
pub data: Message,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the data contained in a message in the Nye wire protocol.
|
||||||
|
#[derive(Debug, Clone, Encode, Decode)]
|
||||||
|
pub enum Message {
|
||||||
|
/// Represents a request message in the Nye wire protocol.
|
||||||
|
Request(MessageRequest),
|
||||||
|
|
||||||
|
/// Represents a response message in the Nye wire protocol.
|
||||||
|
Response(MessageResponse),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message {
|
||||||
|
/// Returns the kind of the message.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`MessageKind`] - the kind of the message.
|
||||||
|
pub fn kind(&self) -> MessageKind {
|
||||||
|
match self {
|
||||||
|
Message::Request(request) => MessageKind::Request(request.kind()),
|
||||||
|
Message::Response(response) => MessageKind::Response(response.kind()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the kind of a message in the Nye wire protocol.
|
||||||
|
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||||
|
pub enum MessageKind {
|
||||||
|
Request(MessageRequestKind),
|
||||||
|
Response(MessageResponseKind),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a request message in the Nye wire protocol.
|
||||||
|
///
|
||||||
|
/// Only sent from the client to the daemon.
|
||||||
|
#[derive(Debug, Clone, Encode, Decode)]
|
||||||
|
pub enum MessageRequest {
|
||||||
|
Setup,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageRequest {
|
||||||
|
/// Returns the kind of the message request.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`MessageRequestKind`] - the kind of the message request.
|
||||||
|
pub fn kind(&self) -> MessageRequestKind {
|
||||||
|
match self {
|
||||||
|
MessageRequest::Setup => MessageRequestKind::Setup,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MessageRequest> for Message {
|
||||||
|
fn from(request: MessageRequest) -> Self {
|
||||||
|
Message::Request(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the kind of a message request in the Nye wire protocol.
|
||||||
|
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||||
|
pub enum MessageRequestKind {
|
||||||
|
Setup,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a response message in the Nye wire protocol.
|
||||||
|
///
|
||||||
|
/// Only sent from the daemon to the client.
|
||||||
|
#[derive(Debug, Clone, Encode, Decode)]
|
||||||
|
pub enum MessageResponse {
|
||||||
|
Setup,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageResponse {
|
||||||
|
/// Returns the kind of the message response.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`MessageResponseKind`] - the kind of the message response.
|
||||||
|
pub fn kind(&self) -> MessageResponseKind {
|
||||||
|
match self {
|
||||||
|
MessageResponse::Setup => MessageResponseKind::SetupFinished,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MessageResponse> for Message {
|
||||||
|
fn from(response: MessageResponse) -> Self {
|
||||||
|
Message::Response(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the kind of a message response in the Nye wire protocol.
|
||||||
|
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||||
|
pub enum MessageResponseKind {
|
||||||
|
SetupFinished,
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
group_imports = "StdExternalCrate"
|
||||||
|
imports_granularity = "Module"
|
||||||
Reference in New Issue
Block a user