Library is almost working but install_package no worky :c

This commit is contained in:
Arthur Beck 2025-03-10 12:30:21 -05:00
parent ba36e3b7fc
commit af2897efb1
Signed by: ArthurB
GPG key ID: ACE3D14F5CEF14BF
4 changed files with 27323 additions and 34 deletions

View file

@ -15,7 +15,9 @@ path = "src/lib/lib.rs"
base64 = "0.22.1"
bytes = "1.10.1"
flate2 = "1.1.0"
gpgme = { version = "0.11.0", features = ["v1_18"] }
reqwest = { version = "0.12.12", features = ["blocking"] }
ruzstd = "0.8.0"
sha256 = "1.6.0"
tar = "0.4.44"
time = { version = "0.3.39", features = ["formatting"] }

26958
src/lib/archlinux.gpg Normal file

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,7 @@
clippy::empty_docs,
clippy::missing_panics_doc
)]
#![feature(str_as_str)]
use std::{
ffi::OsString,
@ -14,6 +15,7 @@ use std::{
use base64::Engine;
use bytes::Buf;
use gpgme::SignatureSummary;
/// A descriptor of a repository.
#[derive(Clone, Default, Debug)]
@ -23,7 +25,7 @@ pub struct RepoDescriptor {
/// The architecture to use.
arch: String,
/// The hash of the (repo).db file.
hash: Option<Vec<u8>>
hash: Option<String>,
}
impl RepoDescriptor {
@ -54,20 +56,21 @@ impl RepoDescriptor {
}
/// The hash of the (repo).db file.
pub fn hash(&self) -> Option<Vec<u8>> {
pub fn hash(&self) -> Option<String> {
self.hash.clone()
}
/// Sets [`hash`].
/// [`hash`]: [RepoDescriptor::hash]
pub fn set_hash(&mut self, hash: Option<Vec<u8>>) -> &mut Self {
pub fn set_hash(&mut self, hash: Option<String>) -> &mut Self {
self.hash = hash;
self
}
/// Formats the repo descriptor into a string.
#[allow(clippy::missing_panics_doc)] // function won't ever actually panic
pub fn format(&self) -> String {
if self.hash().is_some() {
format!("{:x?}-{}-{}", self.hash().unwrap(), self.repo(), self.arch())
format!("{}-{}-{}", self.hash().unwrap(), self.repo(), self.arch())
} else {
format!("{}-{}", self.repo(), self.arch())
}
@ -120,6 +123,7 @@ impl Package {
}
/// A mirror to use.
#[derive(Clone, Debug)]
pub struct Mirror(url::Url);
impl Mirror {
@ -187,16 +191,32 @@ pub fn get_current_user() -> OsString {
}
}
/// Returns the base dir for pacwoman. Use this instead of hard-coding /pacwoman for the case
/// of debugging in a different directory.
///
/// This directory is not guaranteed to exist! If you need it to, use [base_dir]!
pub fn get_base_dir() -> PathBuf {
PathBuf::from("/home/arthur/pacwoman/debug")
}
/// Same as [get_base_dir]; however, this function will create the directory if it doesn't
/// exist. If [std::fs::create_dir_all] returns an error, it will be propagated.
pub fn base_dir() -> std::io::Result<PathBuf> {
let dir = get_base_dir();
std::fs::create_dir_all(&dir)?;
Ok(dir)
}
/// Get the config directory. If for the entire system, then it will be /pacwoman/config, otherwise
/// it will be /pacwomand/user/{user name from [get_current_user]}/config.
///
/// This directory is not guaranteed to exist! If you need it to, use [config_directory]!
pub fn get_config_directory(system: bool) -> PathBuf {
if system {
PathBuf::from("/pacwoman/config")
get_base_dir().join("config")
} else {
PathBuf::from(format!(
"/pacwoman/user/{}/config",
get_base_dir().join(format!(
"user/{}/config",
get_current_user().to_string_lossy()
))
}
@ -211,15 +231,15 @@ pub fn config_directory(system: bool) -> std::io::Result<PathBuf> {
}
/// Get the store directory. If for the entire system, then it will be /pacwoman/store, otherwise
/// it will be /pacwomand/user/{user name from [get_current_user]}/store.
/// it will be /pacwoman/user/{user name from [get_current_user]}/store.
///
/// This directory is not guaranteed to exist! If you need it to, use [store_directory]!
pub fn get_store_directory(system: bool) -> PathBuf {
if system {
PathBuf::from("/home/arthur/pacwoman/store")
get_base_dir().join("store")
} else {
PathBuf::from(format!(
"/home/arthur/pacwoman/user/{}/store",
get_base_dir().join(format!(
"user/{}/store",
get_current_user().to_string_lossy()
))
}
@ -234,6 +254,8 @@ pub fn store_directory(system: bool) -> std::io::Result<PathBuf> {
}
/// Gets the store directory for a repo.
///
/// This directory is not guaranteed to exist! See [repo_store_directory] if you need it too.
pub fn get_repo_store_directory(system: bool, repo: RepoDescriptor) -> PathBuf {
get_store_directory(system).join(repo.format())
}
@ -251,7 +273,7 @@ pub fn repo_store_directory(system: bool, repo: RepoDescriptor) -> std::io::Resu
///
/// This directory is not guaranteed to exist! If you need it to, use [index_directory]!
pub fn get_index_directory() -> PathBuf {
PathBuf::from("/home/arthur/pacwoman/index")
get_base_dir().join("index")
}
/// Same as [get_index_directory]; however, this function will create the directory if it doesn't
@ -278,6 +300,41 @@ pub fn repo_index_dir(repo: RepoDescriptor) -> std::io::Result<PathBuf> {
Ok(dir)
}
/// Directory for storing GPG keys and other miscellaneous GPG stuff.
///
/// This directory is not guaranteed to exist! See [gpg_dir] if you need it too.
pub fn get_gpg_dir() -> PathBuf {
get_base_dir().join("keys")
}
/// Same as [get_gpg_dir], except it will create the directory if it doesn't exist.
/// If [std::fs::create_dir_all] returns an error, it will be propagated.
pub fn gpg_dir() -> std::io::Result<PathBuf> {
let dir = get_gpg_dir();
std::fs::create_dir_all(&dir)?;
Ok(dir)
}
/// Directory for storing binaries. Equivalent to the root directory. Contains directories such
/// as etc, usr, var, and others.
///
/// This directory is not guaranteed to exist! See [bin_dir] if you need it too.
pub fn get_bin_dir(system: bool) -> PathBuf {
if system {
get_base_dir().join("bin")
} else {
get_base_dir().join(format!("user/{}/bin", get_current_user().to_string_lossy()))
}
}
/// Same as [get_bin_dir], except it will create the directory if it doesn't exist.
/// If [std::fs::create_dir_all] returns an error, it will be propagated.
pub fn bin_dir(system: bool) -> std::io::Result<PathBuf> {
let dir = get_bin_dir(system);
std::fs::create_dir_all(&dir)?;
Ok(dir)
}
/// Populates the index with information for a certain repo from a certain mirror. Will
/// overwrite any symlink for the repo.
///
@ -415,7 +472,7 @@ pub fn populate_index(mirror: Mirror, repo: RepoDescriptor) -> std::io::Result<P
/// Get a list of hashes provided by the repo.
#[allow(clippy::missing_panics_doc)] // Shouldn't ever panic, but could be wrong.
pub fn read_repos(repo: RepoDescriptor) -> std::io::Result<Vec<PathBuf>> {
pub fn read_repos(repo: RepoDescriptor) -> std::io::Result<Vec<(PathBuf, String)>> {
let mut repos = std::fs::OpenOptions::new()
.read(true)
.open(index_directory()?.join("REPOS"))?;
@ -433,9 +490,9 @@ pub fn read_repos(repo: RepoDescriptor) -> std::io::Result<Vec<PathBuf>> {
for hash in line.split_once(": ").unwrap().1.split(" ") {
strings.push(hash.to_owned() + &repo.format());
}
let mut out: Vec<PathBuf> = vec![];
let mut out: Vec<(PathBuf, String)> = vec![];
for ele in strings {
out.push(index_directory()?.join(PathBuf::from(ele)));
out.push((index_directory()?.join(PathBuf::from(&ele)), ele));
}
return Ok(out);
}
@ -478,6 +535,9 @@ pub fn locate_package(
let repo = prefix
.strip_suffix(&("-".to_string() + &arch))
.unwrap()
.split_once("-")
.unwrap()
.1
.to_string();
let descriptor = RepoDescriptor::new()
@ -542,10 +602,10 @@ pub struct PackageDesc {
/// MD5 checksum of the package. Recommended not to use and to instead use [`sha256sum`].
/// Stored as hex in the desc file.
/// [`sha256sum`]: [PackageDesc::sha256sum]
pub md5sum: Vec<u8>,
pub md5sum: String,
/// SHA256 checksum of the package.
/// Stored as hex in the desc file.
pub sha256sum: Vec<u8>,
pub sha256sum: String,
/// PGP signature of the package.
/// Stored as base64 in the desc file.
pub pgpsig: Vec<u8>,
@ -640,12 +700,12 @@ pub fn read_desc(package: String, repo: RepoDescriptor) -> std::io::Result<Packa
if let Some(loc) = desc_data.iter().position(|item| (*item) == "%MD5SUM%") {
let loc = loc + 1; // value is on next line
out.md5sum = desc_data[loc].as_bytes().to_vec();
out.md5sum = desc_data[loc].to_string();
}
if let Some(loc) = desc_data.iter().position(|item| (*item) == "%SHA256SUM%") {
let loc = loc + 1; // value is on next line
out.sha256sum = desc_data[loc].as_bytes().to_vec();
out.sha256sum = desc_data[loc].to_string();
}
if let Some(loc) = desc_data.iter().position(|item| (*item) == "%PGPSIG%") {
@ -789,19 +849,56 @@ pub fn read_desc(package: String, repo: RepoDescriptor) -> std::io::Result<Packa
}
}
/// Gets the [gpgme] context and sets the appropriate properties.
pub fn get_gpg_ctx() -> std::io::Result<gpgme::Context> {
let mut gpg_ctx = gpgme::Context::from_protocol(gpgme::Protocol::OpenPgp)?;
gpg_ctx.set_engine_home_dir(gpg_dir()?.to_string_lossy().into_owned())?;
Ok(gpg_ctx)
}
/// The PGP public key file for arch linux signing keys.
const ARCHLINUX_PGP_PUBLIC_KEY: &[u8] = include_bytes!("archlinux.gpg");
/// Initalizes the GPG keyring and other GPG stuff.
pub fn init_gpg() -> std::io::Result<()> {
let mut gpg_ctx = get_gpg_ctx()?;
print!("Generating master key...");
gpg_ctx.generate_key::<Vec<u8>, Vec<u8>>(
r#"<GnupgKeyParms format="internal">
Key-Type: RSA
Key-Length: 4096
Key-Usage: sign
Name-Real: Pacwoman Keyring Master Key
Name-Email: pacwoman@localhost
Expire-Date: 0
%no-protection
</GnupgKeyParms>"#,
None,
None,
)?;
println!(" Done.");
gpg_ctx.import(ARCHLINUX_PGP_PUBLIC_KEY)?;
Ok(())
}
/// Downloads, verifies, and unpacks a package from the provided mirror. The package value
/// provided should be the file name provided in a [PackageDesc].
///
/// # Panics
/// Panics if there are any errors with downloading because right now I don't feel like
/// implementing it to fail gently.
pub fn receive_package(
mirror: Mirror,
repo: RepoDescriptor,
package: String,
system: bool,
) -> std::io::Result<PathBuf> {
if repo.hash().is_none() {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "need hash in repo descriptor"));
}
let path = index_directory()?
.join(repo.format())
.join(package)
.join(&package)
.join("desc");
let mut desc = std::fs::OpenOptions::new().read(true).open(path)?;
@ -814,25 +911,245 @@ pub fn receive_package(
let desc_hash = sha256::digest(desc_data);
let desc = read_desc(package, repo)?;
let desc = read_desc(package.clone(), repo.clone())?;
let url = mirror.substitute(&repo.repo(), &repo.arch(), &desc.filename);
let sig_url = mirror.substitute(
&repo.clone().repo(),
&repo.arch(),
&(desc.filename.clone() + ".sig"),
);
let url = mirror.substitute(&repo.repo(), &repo.arch(), &desc.filename.clone());
let dir = repo_store_directory(system, repo)?.join(format!("{}-{}", desc_hash, package));
if std::fs::exists(index_directory()?.join("RECIEVED"))? {
let mut packages = std::fs::OpenOptions::new()
.read(true)
.open(index_directory()?.join("RECIEVED"))?;
let mut packages_data = String::new();
packages.read_to_string(&mut packages_data)?;
drop(packages);
let packages: Vec<&str> = packages_data.split("\n").collect();
if packages.contains(&format!("{}-{}", desc_hash, package).as_str()) {
return Ok(dir);
}
}
std::fs::create_dir_all(&dir)?;
let sig_data = reqwest::blocking::get(sig_url)
.unwrap()
.bytes()
.unwrap()
.to_vec();
let package_data = reqwest::blocking::get(url)
.unwrap()
.bytes()
.unwrap()
.to_vec();
let mut gpg_ctx = get_gpg_ctx()?;
let res = gpg_ctx
.verify_detached(&sig_data, &package_data)?
.signatures()
.next()
.unwrap()
.summary();
match res {
SignatureSummary::VALID | SignatureSummary::GREEN => {}
sum => {
if !sum.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("signature invalid, error {:#?}", sum),
));
}
}
}
let sum = sha256::digest(&package_data);
if desc.sha256sum != sum {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"SHA256 checksum does not match provided sum in desc, try updating the index or try receiving the package again",
));
}
let mut reader = package_data.reader();
let tar = ruzstd::decoding::StreamingDecoder::new(&mut reader).unwrap();
let mut archive = tar::Archive::new(tar);
archive.unpack(&dir)?;
let mut packages = std::fs::OpenOptions::new()
.create(true)
.append(true)
.truncate(false)
.open(index_directory()?.join("RECIEVED"))?;
packages.write_fmt(format_args!("{}-{}\n", desc_hash, package))?;
drop(packages);
Ok(dir)
}
/// Recieves a package and all of it's dependencies. Returns a list of paths to each package.
pub fn recieve_package_and_dependencies(
mirror: Mirror,
repo: RepoDescriptor,
package: String,
system: bool,
) -> std::io::Result<Vec<PathBuf>> {
let mut out: Vec<PathBuf> = vec![];
out.push(receive_package(
mirror.clone(),
repo.clone(),
package.clone(),
system,
)?);
let path = index_directory()?
.join(repo.clone().format())
.join(&package)
.join("desc");
let mut desc = std::fs::OpenOptions::new().read(true).open(path)?;
let mut desc_data = String::new();
desc.read_to_string(&mut desc_data)?;
drop(desc);
let desc = read_desc(package.clone(), repo.clone())?;
for package in desc.depends {
println!("{}", package);
out.push(receive_package(
mirror.clone(),
repo.clone(),
locate_package(package)?[0].0.clone(),
system,
)?);
}
println!("recieved all dependencies");
Ok(out)
}
/// Recieves a package and it's dependencies and installs it.
#[allow(clippy::missing_panics_doc)] // won't ever actually panic
pub fn install_package(
mirror: Mirror,
repo: RepoDescriptor,
package: String,
system: bool,
) -> std::io::Result<()> {
let out = bin_dir(system)?;
for path in recieve_package_and_dependencies(mirror, repo, package, system)? {
println!("{}", path.to_string_lossy());
println!("{}", out.to_string_lossy());
if std::fs::exists(index_directory()?.join("INSTALLED"))? {
let mut packages = std::fs::OpenOptions::new()
.read(true)
.open(index_directory()?.join("INSTALLED"))?;
let mut packages_data = String::new();
packages.read_to_string(&mut packages_data)?;
drop(packages);
let packages: Vec<&str> = packages_data.split("\n").collect();
if packages.contains(&(path.file_name().unwrap().to_string_lossy().as_str())) {
continue;
}
}
for entry in std::fs::read_dir(&path)? {
println!("entry");
let entry = entry?;
if entry.file_name() == *".BUILDINFO"
|| entry.file_name() == *".MTREE"
|| entry.file_name() == *".PKGINFO"
{
continue;
}
println!("{}", entry.file_name().to_string_lossy());
if entry.file_type()?.is_dir() {
std::fs::create_dir_all(out.join(entry.path()))?;
} else {
std::fs::create_dir_all(out.join(entry.path().parent().unwrap()))?;
std::fs::remove_file(out.join(entry.path()))?;
std::os::unix::fs::symlink(path.join(entry.path()), out.join(entry.path()))?;
}
}
std::os::unix::fs::chroot(&out)?;
if !std::process::Command::new("/usr/bin/bash")
.arg("-c")
.arg("source /.INSTALL && post_install && post_upgrade")
.current_dir(&out)
.spawn()?
.wait()?
.success()
{
println!(
"[WARN] .INSTALL script for package {} failed!",
path.file_name().unwrap().to_string_lossy()
);
}
std::os::unix::fs::chroot(".")?;
std::fs::remove_file(out.join(".INSTALL"))?;
let mut packages = std::fs::OpenOptions::new()
.create(true)
.append(true)
.truncate(false)
.open(index_directory()?.join("INSTALLED"))?;
packages.write_fmt(format_args!(
"{}\n",
path.file_name().unwrap().to_string_lossy()
))?;
drop(packages);
}
Ok(())
}
/// Creates all of the necessary directories, for the system if root or the pacwoman user, and
/// for the current user if not. If [std::fs::create_dir_all] returns an error, it will be
/// propagated.
pub fn create_directories() -> Result<(), std::io::Error> {
if users::get_effective_uid() == 0 || get_current_user() == "pacwoman" {
store_directory(false)?;
config_directory(false)?;
index_directory()?;
} else {
if users::get_effective_uid() == 0
|| get_current_user() == "pacwoman"
|| get_current_user() == "arthur"
{
store_directory(true)?;
config_directory(true)?;
index_directory()?;
gpg_dir()?;
bin_dir(true)?;
} else {
store_directory(false)?;
config_directory(false)?;
bin_dir(false)?;
}
Ok(())

View file

@ -5,7 +5,10 @@
clippy::empty_docs
)]
fn main() {
fn main() -> std::io::Result<()> {
pacwoman::create_directories()?;
//pacwoman::init_gpg()?;
pacwoman::populate_index(
pacwoman::Mirror::new(url::Url::parse(
"https://geo.mirror.pkgbuild.com/$repo/os/$arch",
@ -13,5 +16,14 @@ fn main() {
pacwoman::RepoDescriptor::new()
.set_repo("core".to_string())
.set_arch("x86_64".to_string()).clone(),
).unwrap();
)?;
for package in pacwoman::locate_package("pacman".to_string())? {
println!("found package");
pacwoman::install_package(pacwoman::Mirror::new(url::Url::parse(
"https://geo.mirror.pkgbuild.com/$repo/os/$arch",
).unwrap()), package.1, package.0, true)?;
}
Ok(())
}