CLI is working!
This commit is contained in:
parent
454958e95d
commit
2aa855f5cf
4 changed files with 227 additions and 37 deletions
|
@ -14,8 +14,10 @@ path = "src/lib/lib.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
bytes = "1.10.1"
|
bytes = "1.10.1"
|
||||||
|
clap = { version = "4.5.32", features = ["derive"] }
|
||||||
flate2 = "1.1.0"
|
flate2 = "1.1.0"
|
||||||
gpgme = { version = "0.11.0", features = ["v1_18"] }
|
gpgme = { version = "0.11.0", features = ["v1_18"] }
|
||||||
|
regex = "1.11.1"
|
||||||
reqwest = { version = "0.12.12", features = ["blocking"] }
|
reqwest = { version = "0.12.12", features = ["blocking"] }
|
||||||
ruzstd = "0.8.0"
|
ruzstd = "0.8.0"
|
||||||
sha256 = "1.6.0"
|
sha256 = "1.6.0"
|
||||||
|
|
6
src/default_mirrorlist
Normal file
6
src/default_mirrorlist
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Default mirror list. Only contains global mirrors.
|
||||||
|
# Format is identical to pacman's mirrorlist except that there is no `Server = ` prefix.
|
||||||
|
|
||||||
|
https://geo.mirror.pkgbuild.com/$repo/os/$arch
|
||||||
|
https://ftpmirror.infania.net/mirror/archlinux/$repo/os/$arch
|
||||||
|
https://mirror.rackspace.com/archlinux/$repo/os/$arch
|
|
@ -12,6 +12,7 @@ use std::{
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
os::unix::fs::PermissionsExt
|
||||||
};
|
};
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
@ -197,7 +198,7 @@ pub fn get_current_user() -> OsString {
|
||||||
///
|
///
|
||||||
/// This directory is not guaranteed to exist! If you need it to, use [base_dir]!
|
/// This directory is not guaranteed to exist! If you need it to, use [base_dir]!
|
||||||
pub fn get_base_dir() -> PathBuf {
|
pub fn get_base_dir() -> PathBuf {
|
||||||
PathBuf::from("/home/arthur/pacwoman/debug")
|
PathBuf::from("/pacwoman")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as [get_base_dir]; however, this function will create the directory if it doesn't
|
/// Same as [get_base_dir]; however, this function will create the directory if it doesn't
|
||||||
|
@ -897,6 +898,8 @@ pub fn receive_package(
|
||||||
package: String,
|
package: String,
|
||||||
system: bool,
|
system: bool,
|
||||||
) -> std::io::Result<PathBuf> {
|
) -> std::io::Result<PathBuf> {
|
||||||
|
print!("Attempting to recieve package {}...", package);
|
||||||
|
|
||||||
let path = index_directory()?
|
let path = index_directory()?
|
||||||
.join(repo.format())
|
.join(repo.format())
|
||||||
.join(&package)
|
.join(&package)
|
||||||
|
@ -1005,6 +1008,8 @@ pub fn receive_package(
|
||||||
packages.write_fmt(format_args!("{}-{}\n", desc_hash, package))?;
|
packages.write_fmt(format_args!("{}-{}\n", desc_hash, package))?;
|
||||||
drop(packages);
|
drop(packages);
|
||||||
|
|
||||||
|
println!(" Success.");
|
||||||
|
|
||||||
Ok(dir)
|
Ok(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1016,6 +1021,7 @@ pub fn recieve_package_and_dependencies(
|
||||||
system: bool,
|
system: bool,
|
||||||
) -> std::io::Result<Vec<PathBuf>> {
|
) -> std::io::Result<Vec<PathBuf>> {
|
||||||
let mut out: Vec<PathBuf> = vec![];
|
let mut out: Vec<PathBuf> = vec![];
|
||||||
|
|
||||||
out.push(receive_package(
|
out.push(receive_package(
|
||||||
mirror.clone(),
|
mirror.clone(),
|
||||||
repo.clone(),
|
repo.clone(),
|
||||||
|
@ -1039,7 +1045,6 @@ pub fn recieve_package_and_dependencies(
|
||||||
let desc = read_desc(package.clone(), repo.clone())?;
|
let desc = read_desc(package.clone(), repo.clone())?;
|
||||||
|
|
||||||
for package in desc.depends {
|
for package in desc.depends {
|
||||||
println!("{}", package);
|
|
||||||
out.push(receive_package(
|
out.push(receive_package(
|
||||||
mirror.clone(),
|
mirror.clone(),
|
||||||
repo.clone(),
|
repo.clone(),
|
||||||
|
@ -1047,7 +1052,6 @@ pub fn recieve_package_and_dependencies(
|
||||||
system,
|
system,
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
println!("recieved all dependencies");
|
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1061,9 +1065,6 @@ pub fn install_package(
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
let out = bin_dir(system)?;
|
let out = bin_dir(system)?;
|
||||||
for path in recieve_package_and_dependencies(mirror, repo, package, 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"))? {
|
if std::fs::exists(index_directory()?.join("INSTALLED"))? {
|
||||||
let mut packages = std::fs::OpenOptions::new()
|
let mut packages = std::fs::OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
|
@ -1105,8 +1106,6 @@ pub fn install_package(
|
||||||
.unwrap_or(&entry.path().to_string_lossy()),
|
.unwrap_or(&entry.path().to_string_lossy()),
|
||||||
);
|
);
|
||||||
|
|
||||||
println!("{}", entry_path.clone().to_string_lossy());
|
|
||||||
|
|
||||||
if entry.file_name() == *".BUILDINFO"
|
if entry.file_name() == *".BUILDINFO"
|
||||||
|| entry.file_name() == *".MTREE"
|
|| entry.file_name() == *".MTREE"
|
||||||
|| entry.file_name() == *".PKGINFO"
|
|| entry.file_name() == *".PKGINFO"
|
||||||
|
@ -1143,18 +1142,18 @@ pub fn install_package(
|
||||||
}
|
}
|
||||||
|
|
||||||
if std::fs::exists(out.join(".INSTALL"))? {
|
if std::fs::exists(out.join(".INSTALL"))? {
|
||||||
if users::get_effective_uid() == 0 {
|
if users::get_effective_uid() != 0 {
|
||||||
std::os::unix::fs::chroot(&out)?;
|
println!(
|
||||||
} else {
|
"[WARN] .INSTALL script for package {} is being run without root.",
|
||||||
println!("[WARN] .INSTALL script for package {} is being run without a chroot.", path.file_name().unwrap().to_string_lossy());
|
path.file_name().unwrap().to_string_lossy()
|
||||||
|
);
|
||||||
println!("[WARN] The script may give permission errors or ask for authentication.");
|
println!("[WARN] The script may give permission errors or ask for authentication.");
|
||||||
println!(" [TIP] To run in a chroot, run the program as root.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !std::process::Command::new("/usr/bin/bash")
|
if !std::process::Command::new("/usr/bin/bash")
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg("source ./.INSTALL && post_install && post_upgrade")
|
.arg("source ./.INSTALL && post_install && post_upgrade")
|
||||||
.current_dir(&out)
|
.current_dir("/")
|
||||||
.spawn()?
|
.spawn()?
|
||||||
.wait()?
|
.wait()?
|
||||||
.success()
|
.success()
|
||||||
|
@ -1165,10 +1164,6 @@ pub fn install_package(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if users::get_effective_uid() == 0 {
|
|
||||||
std::os::unix::fs::chroot(".")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fs::remove_file(out.join(".INSTALL"))?;
|
std::fs::remove_file(out.join(".INSTALL"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1188,17 +1183,47 @@ pub fn install_package(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates all of the necessary directories, for the system if root or the pacwoman user, and
|
/// Remove a package from the store. This WILL lead to dangling symlinks because I don't
|
||||||
|
/// feel like fixing that!
|
||||||
|
pub fn remove_package(package: String, system: bool, repo: RepoDescriptor) -> std::io::Result<()> {
|
||||||
|
let path = index_directory()?
|
||||||
|
.join(repo.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_hash = sha256::digest(desc_data);
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(store_directory(system)?.join(format!("{}-{}", desc_hash, package)))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates all of the necessary directories, for the system if running as root, and
|
||||||
/// for the current user if not. If [std::fs::create_dir_all] returns an error, it will be
|
/// for the current user if not. If [std::fs::create_dir_all] returns an error, it will be
|
||||||
/// propagated.
|
/// propagated.
|
||||||
pub fn create_directories() -> Result<(), std::io::Error> {
|
pub fn create_directories(system: bool) -> Result<(), std::io::Error> {
|
||||||
if users::get_effective_uid() == 0 || get_current_user() == "pacwoman" {
|
if system {
|
||||||
store_directory(true)?;
|
store_directory(true)?;
|
||||||
config_directory(true)?;
|
config_directory(true)?;
|
||||||
index_directory()?;
|
index_directory()?;
|
||||||
gpg_dir()?;
|
gpg_dir()?;
|
||||||
bin_dir(true)?;
|
bin_dir(true)?;
|
||||||
|
let user_dir = base_dir()?.join("user");
|
||||||
|
std::fs::create_dir_all(&user_dir)?;
|
||||||
|
std::fs::set_permissions(user_dir, std::fs::Permissions::from_mode(0o777))?;
|
||||||
} else {
|
} else {
|
||||||
|
let user_dir = base_dir()?.join("user").join(get_current_user());
|
||||||
|
std::fs::create_dir_all(&user_dir)?;
|
||||||
|
std::fs::set_permissions(user_dir, std::fs::Permissions::from_mode(0o700))?;
|
||||||
|
|
||||||
store_directory(false)?;
|
store_directory(false)?;
|
||||||
config_directory(false)?;
|
config_directory(false)?;
|
||||||
bin_dir(false)?;
|
bin_dir(false)?;
|
||||||
|
|
189
src/main.rs
189
src/main.rs
|
@ -1,28 +1,185 @@
|
||||||
//! Main binary entrypoint.
|
//! An alternate client for the arch linux package repositories. Stores files in a
|
||||||
|
//! separate directory from everything else and keeps everything hashed.
|
||||||
#![warn(
|
#![warn(
|
||||||
missing_docs,
|
missing_docs,
|
||||||
clippy::missing_docs_in_private_items,
|
clippy::missing_docs_in_private_items,
|
||||||
clippy::empty_docs
|
clippy::empty_docs
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
/// Parser for command line arguments
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(version, about, long_about = None, name = "pacwoman")]
|
||||||
|
struct Cli {
|
||||||
|
/// Whether to operate as a user or not. In most cases, running as system will
|
||||||
|
/// require running as root.
|
||||||
|
#[arg(short, long, default_value_t = false)]
|
||||||
|
user: bool,
|
||||||
|
/// Initalize the GPG keyring and create skeleton directories. This will also add
|
||||||
|
/// a default configuration for the mirrors.
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
initalize: bool,
|
||||||
|
/// Download one or more packages. This WILL NOT install them to your bin directory!
|
||||||
|
#[arg(id = "packages to download", short = 'D')]
|
||||||
|
download: Vec<String>,
|
||||||
|
/// Download and install one or more packages. This WILL NOT update the repositories if needed!
|
||||||
|
#[arg(id = "packages to sync", short = 'S')]
|
||||||
|
sync: Vec<String>,
|
||||||
|
/// Remove a package. Because I'm lazy, this will only remove the store entry, leaving a bunch
|
||||||
|
/// of dangling symlinks.
|
||||||
|
#[arg(id = "packages to remove", short = 'R')]
|
||||||
|
remove: Vec<String>,
|
||||||
|
/// Sync repository indexes from mirrors.
|
||||||
|
#[arg(id = "repositories to sync", short = 'Y')]
|
||||||
|
sync_repos: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default mirrorlist configuration.
|
||||||
|
const DEFAULT_MIRRORLIST: &[u8] = include_bytes!("default_mirrorlist");
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
pacwoman::create_directories()?;
|
let args = Cli::parse();
|
||||||
//pacwoman::init_gpg()?;
|
|
||||||
|
|
||||||
pacwoman::populate_index(
|
if args.initalize {
|
||||||
pacwoman::Mirror::new(url::Url::parse(
|
pacwoman::create_directories(!args.user)?;
|
||||||
"https://geo.mirror.pkgbuild.com/$repo/os/$arch",
|
pacwoman::init_gpg()?;
|
||||||
).unwrap()),
|
|
||||||
pacwoman::RepoDescriptor::new()
|
|
||||||
.set_repo("core".to_string())
|
|
||||||
.set_arch("x86_64".to_string()).clone(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
for package in pacwoman::locate_package("pacman".to_string())? {
|
std::fs::write(
|
||||||
println!("found package");
|
pacwoman::config_directory(!args.user)?.join("mirrors"),
|
||||||
pacwoman::install_package(pacwoman::Mirror::new(url::Url::parse(
|
DEFAULT_MIRRORLIST,
|
||||||
"https://geo.mirror.pkgbuild.com/$repo/os/$arch",
|
)?;
|
||||||
).unwrap()), package.1, package.0, false)?;
|
}
|
||||||
|
|
||||||
|
let regex = regex::Regex::new(r"(?m)#.*$").unwrap();
|
||||||
|
|
||||||
|
let mirrors: Vec<pacwoman::Mirror> = regex
|
||||||
|
.replace_all(
|
||||||
|
&(std::fs::read_to_string(pacwoman::config_directory(!args.user)?.join("mirrors"))?),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
.split("\n")
|
||||||
|
.filter(|item| item.trim() != "")
|
||||||
|
.map(|item| pacwoman::Mirror::new(url::Url::parse(item).unwrap()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !args.sync_repos.is_empty() {
|
||||||
|
for repo in &args.sync_repos {
|
||||||
|
let mut ok = false;
|
||||||
|
for mirror in &mirrors {
|
||||||
|
if pacwoman::populate_index(
|
||||||
|
mirror.clone(),
|
||||||
|
pacwoman::RepoDescriptor::new()
|
||||||
|
.set_repo(repo.clone())
|
||||||
|
.set_arch(std::env::consts::ARCH.to_string())
|
||||||
|
.clone(),
|
||||||
|
).is_ok() {
|
||||||
|
ok = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
println!(" [ERR] Cannot download repo {repo}!");
|
||||||
|
println!(" [TIP] Check the spelling and your mirrors.");
|
||||||
|
|
||||||
|
drop(mirrors);
|
||||||
|
drop(args);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !args.download.is_empty() {
|
||||||
|
for package in &args.download {
|
||||||
|
let results = pacwoman::locate_package(package.clone())?;
|
||||||
|
if results.is_empty() {
|
||||||
|
println!(" [ERR] Cannot locate packages for {package}!");
|
||||||
|
println!(" [TIP] Try adding a repository to the index.");
|
||||||
|
|
||||||
|
drop(mirrors);
|
||||||
|
drop(args);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
for result in results {
|
||||||
|
let mut ok = false;
|
||||||
|
for mirror in &mirrors {
|
||||||
|
if pacwoman::recieve_package_and_dependencies(
|
||||||
|
mirror.clone(),
|
||||||
|
result.1.clone(),
|
||||||
|
result.0.clone(),
|
||||||
|
!args.user,
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
ok = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
println!(" [ERR] Cannot download package {}!", result.0);
|
||||||
|
println!(" [TIP] Check the spelling and try syncing repos.");
|
||||||
|
|
||||||
|
drop(mirrors);
|
||||||
|
drop(args);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !args.sync.is_empty() {
|
||||||
|
for package in &args.sync {
|
||||||
|
let results = pacwoman::locate_package(package.clone())?;
|
||||||
|
if results.is_empty() {
|
||||||
|
println!(" [ERR] Cannot locate packages for {package}!");
|
||||||
|
println!(" [TIP] Try adding a repository to the index.");
|
||||||
|
|
||||||
|
drop(mirrors);
|
||||||
|
drop(args);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
for result in results {
|
||||||
|
let mut ok = false;
|
||||||
|
for mirror in &mirrors {
|
||||||
|
if pacwoman::install_package(
|
||||||
|
mirror.clone(),
|
||||||
|
result.1.clone(),
|
||||||
|
result.0.clone(),
|
||||||
|
!args.user,
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
ok = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
println!(" [ERR] Cannot sync package {}!", result.0);
|
||||||
|
println!(" [TIP] Check the spelling and try syncing repos.");
|
||||||
|
|
||||||
|
drop(mirrors);
|
||||||
|
drop(args);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !args.remove.is_empty() {
|
||||||
|
for package in &args.remove {
|
||||||
|
let results = pacwoman::locate_package(package.clone())?;
|
||||||
|
if results.is_empty() {
|
||||||
|
println!(" [ERR] Cannot locate packages for {package}!");
|
||||||
|
println!(" [TIP] Try adding a repository to the index.");
|
||||||
|
|
||||||
|
drop(mirrors);
|
||||||
|
drop(args);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
for result in results {
|
||||||
|
pacwoman::remove_package(result.0, !args.user, result.1)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Add table
Reference in a new issue