Checkpoint
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user