Checkpoint

This commit is contained in:
2026-08-02 18:30:34 +00:00
parent 596af4ed80
commit 6c33bcfb27
41 changed files with 6360 additions and 1 deletions
+15
View File
@@ -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"] }
+8
View File
@@ -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,
}
+1
View File
@@ -0,0 +1 @@
pub mod setup;
+84
View File
@@ -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(())
}
View File
+106
View File
@@ -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")
}
+58
View File
@@ -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(())
}
+51
View File
@@ -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)
}
}