diff --git a/.gitignore b/.gitignore index be024c4..7c9f50c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +**/dist/ **/target **/Cargo.lock *.pkg \ No newline at end of file diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml new file mode 100644 index 0000000..f5ad76c --- /dev/null +++ b/.idea/jsonSchemas.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 7e24755..42323c9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,47 @@ # Packager +[![CI Status](https://ci.ixvd.net/api/badges/2/status.svg)](https://ci.ixvd.net/repos/2) + > "A package manager and builder but like rust." > -- Gandhi (2050) -> ***not even close to done*** :) +> ***Almost to a point you can use it*** :) + +Packager is a simple yet powerful package manager + +## Install a package +pkgr supports fetching packages with a: +- path +- url (http/https) +- package locator + +```shell +# example with path +pkgr install ./foxy/snek.pkg +# example with url +pkgr install https://example.com/doggo.pkg +# example with package locator +pkgr install foo:stable,bar +``` + +## Composing a package +Right now, pkgr does not have a compose/package command. (it's in the works dw!) +You can create a package with `pkg.py`! + +```shell +# Usage: pkg.py [ ...] +# example: +python pkg.py manifest.toml example.pkg root/ source/ scripts/ +# this will create a package with an archive that look like this: +# [header+manifest] +# ... +# [archive: +# root/ +# source/ +# scripts/ +# ] +``` + +It's not perfect, but it'll get the job done :) + -Packager is a simple yet powerful package manager \ No newline at end of file diff --git a/bodge-pkg.py b/bodge-pkg.py deleted file mode 100644 index cca3307..0000000 --- a/bodge-pkg.py +++ /dev/null @@ -1,31 +0,0 @@ -import os -import tarfile - -with open("./package.toml", mode='r') as mani: - data = mani.read() - with open("./sample.pkg", mode='wb') as pkg: - print("building header...") - pkg.write(bytes([0x01, (len(data) >> 8) & 0xFF, len(data) & 0xFF])) - print("writing manifest into pkg...") - pkg.write(data.encode("utf-8")) - with tarfile.TarFile("/tmp/pkgtar", 'w') as pkgtar: - print("tarring ./pkg...") - os.chdir("pkg") - for root, dirs, files in os.walk("."): - for file in files: - print(f"\33[2Kadd: {os.path.join(root, file)}", end="\r", flush=True) - pkgtar.add(os.path.join(root, file)) - os.chdir("..") - for root, dirs, files in os.walk("pkgr"): - for file in files: - print(f"\33[2Kadd: {os.path.join(root, file)}", end="\r", flush=True) - pkgtar.add(os.path.join(root, file)) - print("\33[2K", end="\r", flush=True) - with open("/tmp/pkgtar", 'rb') as pkgtar: - print("appending pkgtar to pkg...") - pkg.write(pkgtar.read()) - print("deleting /tmp/pkgtar...") - os.unlink("/tmp/pkgtar") - print("closing write stream") - pkg.close() - \ No newline at end of file diff --git a/bootpkg/Cargo.toml b/bootpkg/Cargo.toml index d6c3186..5530ce3 100644 --- a/bootpkg/Cargo.toml +++ b/bootpkg/Cargo.toml @@ -14,3 +14,4 @@ regex = "1.9.1" reqwest = { version = "0.11.18", features = ["blocking"] } uuid = { version = "1.4.0", features = ["serde", "v4"] } tar = "0.4.39" +libc = "0.2.80" diff --git a/bootpkg/package.toml b/bootpkg/package.toml new file mode 100644 index 0000000..c272a08 --- /dev/null +++ b/bootpkg/package.toml @@ -0,0 +1,10 @@ +[package] +name = "bootpkg" #* +description = "A tool to strap pkgs" #* +version = 1 +tags = [] +type = "application" +arch = "x86_64" + +[bin] +root = "/root" \ No newline at end of file diff --git a/bootpkg/src/args.rs b/bootpkg/src/args.rs index 0df76c9..373830b 100644 --- a/bootpkg/src/args.rs +++ b/bootpkg/src/args.rs @@ -18,9 +18,20 @@ pub struct Args { impl From> for Args { fn from(value: Vec) -> Self { + if value.len() == 0 { + return Args { + command: Command::from(String::default()), + args: vec![], + } + } + Args { command: Command::from(value[0].to_owned()), - args: value[1..].to_owned(), + args: if value.len() > 1 { + value[1..].to_owned() + } else { + vec![] + }, } } } diff --git a/bootpkg/src/main.rs b/bootpkg/src/main.rs index ffd4338..65037f3 100644 --- a/bootpkg/src/main.rs +++ b/bootpkg/src/main.rs @@ -15,6 +15,14 @@ mod args; mod prelude; fn main() { + #[cfg(not(debug_assertions))] + { + if unsafe { libc::getuid() } != 0 { + println!("bootpkg must be run as root."); + std::process::exit(1); + } + } + let args = Args::from(env::args().collect::>()[1..].to_owned()); match args.command { Command::Strap => { diff --git a/bootpkg/src/prelude.rs b/bootpkg/src/prelude.rs index 9406cdb..b5ec6fe 100644 --- a/bootpkg/src/prelude.rs +++ b/bootpkg/src/prelude.rs @@ -30,12 +30,12 @@ pub fn mani_from_str(s: &str) -> Manifest> { fs: mani.fs, bin: mani.bin, build: mani.build, - pkgr: bmani.pkgr, + ext: bmani.pkgr, } } pub fn run_bootstrap(mani: Manifest>) -> bool { - if let Some(pkgr) = mani.pkgr { + if let Some(pkgr) = mani.ext { if let Some(bootstrap) = pkgr.bootstrap { fn run_command>(s: S) -> i32 { std::process::Command::new("sh") diff --git a/build b/build new file mode 100755 index 0000000..c245f7a --- /dev/null +++ b/build @@ -0,0 +1,39 @@ +#!/bin/bash + +build_pkgr() { + cd pkgr + mkdir -p dist/root/{usr/bin,etc/pkgr} + echo -e "You can't use pkgr to update pkgr because the file will be in use while updating.\nuse bootpkg" > dist/root/etc/pkgr.d/YOU-CAN-NOT-USE-PKGR-TO-UPDATE-PKGR.txt + + # for bin + cargo build -r + cp target/release/pkgr dist/root/usr/bin/pkgr + + # for build + mkdir -p dist/pkgr + cp -r src/ Cargo.toml dist/pkgr + cp -r ../manifest dist/manifest + cp -r ../pkgfile dist/pkgfile + + cd dist + python ../../pkg.py ../package.toml pkgr.pkg "*" + cd ../.. +} + +build_bootpkg() { + cd bootpkg + mkdir -p dist/root/usr/bin + + cargo build -r + cp target/release/bootpkg dist/root/usr/bin/bootpkg + + + cd dist + python ../../pkg.py ../package.toml bootpkg.pkg "*" + cd ../.. +} + +set -e + +build_bootpkg +build_pkgr diff --git a/docs/README.md b/docs/README.md index 0e9b295..5b84964 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,7 +9,9 @@ pkgr is the main tool for the packager. Because this tool is so feature rich, it has its own [README](./pkgr/README.md). ## bootpkg -bootpkg is a tool primariliy used to bootstrap the packager. +`bootpkg` is a simple pkgr variant. +It doesn't contain anything fancy and is only used to install and update pkgr. +Using it for anything other than pkgr is not recommended. ### Usage @@ -17,4 +19,4 @@ bootpkg is a tool primariliy used to bootstrap the packager. bootpkg strap ./pkgr.pkg ``` -This will extract the pkgfile and read the `[pkgr.bootstrap]` section to bootstrap the packager or any other package. \ No newline at end of file +This will extract the pkgfile and read the `[pkgr.bootstrap]` section to bootstrap the packager or any other package. diff --git a/docs/manifest.md b/docs/manifest.md new file mode 100644 index 0000000..d6f7f14 --- /dev/null +++ b/docs/manifest.md @@ -0,0 +1,5 @@ +# Manifest + +--- + +The manifest spec is located in `/package.example.toml` as an example. \ No newline at end of file diff --git a/docs/pkgfile.md b/docs/pkgfile.md deleted file mode 100644 index 4b2b81a..0000000 --- a/docs/pkgfile.md +++ /dev/null @@ -1,24 +0,0 @@ -# PKGFILE -This file is essentially a tar with the manifest in the header. - -## Format -The format is as follows: -``` -[PKGFILE version (1 byte)][manifest length x 256 (1 byte)][manifest length (1 byte)] -[manifest (manifest length bytes)] -[archive] -``` - -The file is Big Endian. - -## Unpacking -To unpack a PKGFILE, you can use the following command: -```bash -pkgr unpack -``` - -## Packing -You can write your own packer, or use the following command: -```bash -pkgr pack -``` diff --git a/docs/pkgfiles/current.md b/docs/pkgfiles/current.md new file mode 100644 index 0000000..e5cd8cc --- /dev/null +++ b/docs/pkgfiles/current.md @@ -0,0 +1,25 @@ +# PKGFILE + +version 1 + +--- +PKGFiles are an all-in-one solution to install a package. + +## Overhead + +Because pkgfiles include everything, many times you're downloading more than what is needed. +This may seem stupid but pkgfiles are *not* meant to stay on your system. + +## Format + +The format is as follows: + +``` +[PKGFILE version (1 byte)][manifest length x 256 (1 byte)][manifest length (1 byte)] +[manifest (manifest length bytes)] +[archive] +``` + +### Endianness + +The file is Big Endian. diff --git a/docs/pkgfiles/planned.md b/docs/pkgfiles/planned.md new file mode 100644 index 0000000..398f8da --- /dev/null +++ b/docs/pkgfiles/planned.md @@ -0,0 +1,22 @@ +# Planned PKGFILE features. + +--- + +### Compression + +> Planned for next PKGFILE version. + +Currently, compression is not supported, since PKGFILE is an all-in-one solution this is very unpleasant. +When compression will be implemented, there is a big possibility that the manifest won't be compressed. +This is done to allow servers to easily parse and inspect manifests. +The compression will likely be versatile in the way that the format will be stated in the header and the pkgr +implementation can work it out by itself. +There will be a default compression format, likely gzip. + +### Compact Manifest + +> Not planned yet. + +Right now, the manifest is an utf-8 blob sandwiched between the header and the archive. +This also requires a fixed max size for the manifest which is unfavourable. +In the future, the manifest may turn into a versatile binary blob with no size limitations. \ No newline at end of file diff --git a/manifest/src/bin.rs b/manifest/src/bin.rs index f55a8e5..5142d99 100644 --- a/manifest/src/bin.rs +++ b/manifest/src/bin.rs @@ -5,5 +5,15 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Bin { pub root: String, + #[serde(default)] pub checksums: HashMap, } + +impl Default for Bin { + fn default() -> Self { + Bin { + root: String::default(), + checksums: HashMap::default(), + } + } +} diff --git a/manifest/src/build.rs b/manifest/src/build.rs index 1879710..6d406b1 100644 --- a/manifest/src/build.rs +++ b/manifest/src/build.rs @@ -8,3 +8,13 @@ pub struct Build { pub install_script: String, pub dependencies: HashMap, } + +impl Default for Build { + fn default() -> Self { + Build { + dependencies: HashMap::default(), + build_script: String::default(), + install_script: String::default() + } + } +} \ No newline at end of file diff --git a/manifest/src/pkgr.rs b/manifest/src/ext.rs similarity index 80% rename from manifest/src/pkgr.rs rename to manifest/src/ext.rs index 530a37c..b47a60c 100644 --- a/manifest/src/pkgr.rs +++ b/manifest/src/ext.rs @@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PKGR {} +pub struct Extension {} -impl Display for PKGR { +impl Display for Extension { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "") } diff --git a/manifest/src/lib.rs b/manifest/src/lib.rs index 0d52840..89526f2 100644 --- a/manifest/src/lib.rs +++ b/manifest/src/lib.rs @@ -6,17 +6,17 @@ pub mod bin; pub mod build; pub mod fs; pub mod package; -pub mod pkgr; +pub mod ext; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] -pub struct Manifest> { +pub struct Manifest> { pub package: package::Package, pub dependencies: HashMap, pub fs: fs::FS, pub bin: Option, pub build: Option, - pub pkgr: P, + pub ext: E, } impl Manifest

{ @@ -50,7 +50,7 @@ impl Default for Manifest { fs: fs::FS::default(), bin: None, build: None, - pkgr: None, + ext: None, } } } diff --git a/manifest/src/package.rs b/manifest/src/package.rs index 233206d..d7c126c 100644 --- a/manifest/src/package.rs +++ b/manifest/src/package.rs @@ -12,6 +12,16 @@ pub enum PackageType { Meta, } +impl ToString for PackageType { + fn to_string(&self) -> String { + match &self { + PackageType::Application => "application", + PackageType::Library => "library", + PackageType::Meta => "meta" + }.to_string() + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct Package { diff --git a/package.example.toml b/package.example.toml index c99108d..c20aabb 100644 --- a/package.example.toml +++ b/package.example.toml @@ -84,16 +84,16 @@ install_script = "scripts/install" # relative to pkg [build.dependencies] base = "latest,stable" # selected by default -## pkgr.* +## ext.* # packager is the official client but you may use other clients supporting the "pkgr v1 spec". -# other clients may offer extra functionality this must be put under "pkgr.*" -[pkgr] +# other clients may offer extra functionality this must be put under "ext.*" +[ext] -## pkgr.bootstrap +## ext.bootstrap # This section is used for bootpkg. An edition of packager that bootstraps the full version. # This exists so that packager is easy to install on anything! # and only 1 release channel for pkgr -[pkgr.bootstrap] +[ext.bootstrap] ## any non-zero = installed check_installed_commands = [ "sh scripts/check_installed" diff --git a/package.toml b/package.toml deleted file mode 100644 index 81240ab..0000000 --- a/package.toml +++ /dev/null @@ -1,69 +0,0 @@ -[package] -name = "packager" #* -description = "A package installation tool" #* -version = 1 # this can automatically be incremented when publishing by running `pkgr publish -i ...` -tags = [ "prod", "pkgr-spec-1" ] -type = "application" - -arch = "x86_64" # this is automatically filled by `pkgr publish ...` - -## dependencies -# you may use the following syntax -# "" or ",,..." -[dependencies] - -## bin -# Used for systems that don't want to build pkgs. -[bin] # binary files root - -## bin.root -# ** RELATIVE TO PACKAGE ROOT ** -# bin.root specifies the root of the installed tree. -# anything in here will be overlayed on top of the system. -root = "/root" #* - -## bin.checksums -# ** KEYS are relative to BIN.ROOT ** -# ** VALUES are relative to PKG ROOT ** -# checksums is used to perform checksums before install -# values may be paths or a uri. You may include variables. -# supported variables: -# - @name -# - @version -# - @display_version -[bin.checksums] -"/usr/bin/pkgr" = "https://ixvd.net/checksums/packager/@version" - -## build -# Scripts will be copied to a custom root. -# After the pacakge is built, the install script is ran. -# After the install script the build directory will be deleted and an CTREE (changed tree) will be generated. -# Then the CTREE will be used to copy the files over. -[build] -build_script = "scripts/build" # relative to pkg -install_script = "scripts/install" # relative to pkg - -[build.dependencies] -base = "latest,stable" # selected by default - -## pkgr.* -# packager is the official client but you may use other clients supporting the "pkgr v1 spec". -# other clients may offer extra functionality this must be put under "pkgr.*" -[pkgr] - -## pkgr.bootstrap -# This section is used for bootpkg. An edition of packager that bootstraps the full version. -# This exists so that packager is easy to install on anything! -# and only 1 release channel for pkgr -[pkgr.bootstrap] -## any non-zero = installed -check_installed_commands = [ - "sh scripts/check_installed" -] - -# any non-zero = fail -commands = [ - "sh scripts/bootstrap/download_latest @version /tmp/pkgr.pkg", - "sh scripts/bootstrap/dirty_install /tmp/pkgr.pkg", - "sh scripts/check_installed" -] diff --git a/pkg.py b/pkg.py new file mode 100644 index 0000000..03e20ab --- /dev/null +++ b/pkg.py @@ -0,0 +1,79 @@ +import glob +import os +import sys +import tarfile + + +def build_package(package_toml_path, output_path, directories_to_include): + data = read_package_toml(package_toml_path) + header = build_header(data) + pkg_path = output_path + + with open(pkg_path, mode='wb') as pkg: + pkg.write(header) + write_manifest(pkg, data) + build_tarball(pkg, directories_to_include) + + return pkg_path + + +def read_package_toml(package_toml_path): + with open(package_toml_path, mode='r') as mani: + return mani.read() + + +def build_header(data): + header = bytes([0x01, (len(data) >> 8) & 0xFF, len(data) & 0xFF]) + return header + + +def write_manifest(pkg, data): + pkg.write(data.encode("utf-8")) + + +def build_tarball(pkg, directories_to_include): + with tarfile.TarFile("/tmp/pkgtar", 'w') as pkgtar: + for directory in directories_to_include: + add_files_to_tar(pkgtar, directory, True) + + append_tar_to_pkg(pkg) + cleanup_tmp_files() + + +def add_files_to_tar(pkgtar, directory, skip_target=False): + for root, dirs, files in os.walk(directory): + for file in files: + if skip_target and "target" in root: + continue + print(f"\33[2Kadd: {os.path.join(root, file)}", end="\r", flush=True) + # print() + pkgtar.add(os.path.join(root, file)) + print("\33[2K", end="\r", flush=True) + + +def append_tar_to_pkg(pkg): + with open("/tmp/pkgtar", 'rb') as pkgtar: + pkg.write(pkgtar.read()) + + +def cleanup_tmp_files(): + print("deleting /tmp/pkgtar...") + os.unlink("/tmp/pkgtar") + + +if __name__ == '__main__': + if len(sys.argv) < 3: + print("Usage: pkg.py [ ...]") + sys.exit(1) + + package_toml_path = sys.argv[1] + output_path = sys.argv[2] + directories_to_include = glob.glob(sys.argv[3]) if len(sys.argv) == 4 else sys.argv[3:] + + try: + pkg_path = build_package(package_toml_path, output_path, directories_to_include) + print(f"Package created: {pkg_path}") + except FileNotFoundError: + print("Error: File not found.") + except Exception as e: + print(f"Error occurred: {e}") diff --git a/pkg/scripts/bootstrap/dirty_install b/pkg/scripts/bootstrap/dirty_install deleted file mode 100644 index 41d3f5d..0000000 --- a/pkg/scripts/bootstrap/dirty_install +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -printf "" \ No newline at end of file diff --git a/pkg/scripts/bootstrap/download_latest b/pkg/scripts/bootstrap/download_latest deleted file mode 100644 index 178bd4e..0000000 --- a/pkg/scripts/bootstrap/download_latest +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -fetch_latest_version() { - printf "" -} - -download_file() { - printf "" -} \ No newline at end of file diff --git a/pkg/scripts/check_installed b/pkg/scripts/check_installed deleted file mode 100644 index 41d3f5d..0000000 --- a/pkg/scripts/check_installed +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -printf "" \ No newline at end of file diff --git a/pkgfile/src/lib.rs b/pkgfile/src/lib.rs index 407055c..6a9457d 100644 --- a/pkgfile/src/lib.rs +++ b/pkgfile/src/lib.rs @@ -1,4 +1,9 @@ -#[derive(Debug, Clone)] +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::io; +use std::path::Path; + +#[derive(Debug, Clone, Eq, PartialEq)] pub struct PKGFile { pub manifest: String, pub data: Vec, @@ -23,28 +28,56 @@ impl Default for PKGFile { } } -impl TryFrom> for PKGFile { - type Error = (); +#[derive(Debug)] +pub enum PKGFileError { + IOError(io::Error), + ParsingError(String) +} - fn try_from(value: Vec) -> Result { +impl Display for PKGFileError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PKGFileError::IOError(e) => write!(f, "IOError: {}", e), + PKGFileError::ParsingError(s) => write!(f, "ParsingError: {}", s) + } + } +} + +impl Error for PKGFileError {} + +impl<'a> TryFrom<&'a Path> for PKGFile { + type Error = PKGFileError; + fn try_from(path: &'a Path) -> Result { + let d = match std::fs::read(path) { + Ok(d) => d, + Err(e) => return Err(PKGFileError::IOError(e)) + }; + PKGFile::try_from(d) + } +} + +impl TryFrom> for PKGFile { + type Error = PKGFileError; + + fn try_from(value: Vec) -> Result { match value[0] { 1 => { let header: Vec = value[..3].iter().map(|v| u32::from(*v)).collect(); let manifest_size: u32 = (header[1] << 8) | header[2]; if manifest_size > value.len() as u32 { - return Err(()); + return Err(PKGFileError::ParsingError("Invalid header length".into())); } Ok(PKGFile { manifest: match String::from_utf8( value[3..(manifest_size as usize + 3)].to_vec(), ) { Ok(s) => s, - _ => return Err(()), + _ => return Err(PKGFileError::ParsingError("Could not parse manifest".into())), }, data: value[(manifest_size as usize + 3)..].to_vec(), }) } - _ => Err(()), + _ => Err(PKGFileError::ParsingError("Unknown pkgfile version".into())), } } } diff --git a/pkgr/Cargo.toml b/pkgr/Cargo.toml index cc6e55b..e43b436 100644 --- a/pkgr/Cargo.toml +++ b/pkgr/Cargo.toml @@ -23,3 +23,7 @@ serde = { version = "1.0.171", features = ["derive"] } libc = "0.2.80" reqwest = { version = "0.11.18", features = ["blocking"] } tar = "0.4.39" +humantime = "2.1.0" +expanduser = "1.2.2" +url = { version = "2.4.0", features = ["serde"] } +dns-lookup = "2.0.3" diff --git a/pkgr/package.toml b/pkgr/package.toml new file mode 100644 index 0000000..fa95cd9 --- /dev/null +++ b/pkgr/package.toml @@ -0,0 +1,32 @@ +[package] +name = "packager" +description = "A package installation tool" +version = 1 +tags = [ "prod", "pkgr-spec-1" ] +type = "application" +arch = "x86_64" + +[dependencies] + +[bin] +root = "/root" + +[bin.checksums] +"/usr/bin/pkgr" = "https://ixvd.net/checksums/packager/@version" + +[build] +build_script = "scripts/build" +install_script = "scripts/install" + +[build.dependencies] + +[ext] + +[ext.bootstrap] +check_installed_commands = [ + "file /usr/bin/pkgr" +] + +commands = [ + "cp root/usr/bin/pkgr /usr/bin/pkgr" +] diff --git a/pkgr/skel/etc/pkgr.d/repos.toml b/pkgr/skel/etc/pkgr.d/repos.toml new file mode 100644 index 0000000..2723e7b --- /dev/null +++ b/pkgr/skel/etc/pkgr.d/repos.toml @@ -0,0 +1,4 @@ +[repo.main] +name = "Main" +url = "tcp://pkgs.ixvd.net:1050" + diff --git a/pkgr/skel/etc/pkgr.toml b/pkgr/skel/etc/pkgr.toml new file mode 100644 index 0000000..2ee5004 --- /dev/null +++ b/pkgr/skel/etc/pkgr.toml @@ -0,0 +1,7 @@ +build_by_default = false +tmp_dir = "/tmp/pkgr" + +[storage] +repo_file = "/etc/pkgr.d/repos.toml" +data_dir = "/var/lib/pkgr/packages" +index_dir = "/var/lib/pkgr/indexes" \ No newline at end of file diff --git a/pkgr/skel/var/lib/pkgr/indexes/repo.toml b/pkgr/skel/var/lib/pkgr/indexes/repo.toml new file mode 100644 index 0000000..47fa234 --- /dev/null +++ b/pkgr/skel/var/lib/pkgr/indexes/repo.toml @@ -0,0 +1,20 @@ +[index.main] +last_update = "1610219288" +[index.main.packager] +versions = [1, 2, 3] +tags = ["tag"] + +## rust +# IndexedPackage +# - name -> packager +# - origin_repo -> main +# - versions -> [1,2,3] +# - tags -> ["tag"] +# - get_package() -> Package + +## rust +# Repo +# - name -> main +# - last_update -> 1610219288 +# - update_repo() -> io::Result<()> +# - searchIndex(p: PackageIdentifier) -> IndexedPackage \ No newline at end of file diff --git a/pkgr/src/api/client.rs b/pkgr/src/api/client.rs new file mode 100644 index 0000000..e69de29 diff --git a/pkgr/src/api/mod.rs b/pkgr/src/api/mod.rs new file mode 100644 index 0000000..1ed3d70 --- /dev/null +++ b/pkgr/src/api/mod.rs @@ -0,0 +1,63 @@ +use std::collections::HashMap; +use serde; +use serde::{Deserialize, Serialize}; + +pub mod client; + +#[derive(Serialize, Deserialize)] +pub enum Query { + #[serde(rename = "pull")] + Pull { + name: String, + version: Option, + tags: Vec + }, + #[serde(rename = "push")] + Push { + // todo: review me pls + _data: Vec + }, + #[serde(rename = "index")] + Index { + request_update: bool, + fetch: bool + } +} + +#[derive(Serialize, Deserialize)] +pub struct Request { + version: u32, + id: String, + token: Option, + query: HashMap +} + +#[derive(Serialize, Deserialize)] +pub struct DataErrorDetails { + actor: String, + detailed_cause: String, + recovery_options: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct DataError { + name: String, + cause: Option, + details: Option +} + +#[derive(Serialize, Deserialize)] +pub enum Data { + Pull { + _data: Option>, + } +} + +#[derive(Serialize, Deserialize)] +pub struct Response { + version: u32, + id: String, + reply_to: String, + errors: HashMap, + data: HashMap +} \ No newline at end of file diff --git a/pkgr/src/commands.rs b/pkgr/src/commands.rs index a1904a7..80e9448 100644 --- a/pkgr/src/commands.rs +++ b/pkgr/src/commands.rs @@ -1,12 +1,17 @@ -use crate::package::identifier::PackageIdentifier; +use std::process::exit; -use crate::package::Package; use clap::{Parser, Subcommand}; +use colored::Colorize; use log::{debug, error, info, trace, warn}; -use std::process::exit; -use crate::CONFIG; +use manifest::package::PackageType; + +use crate::package::identifier::PackageIdentifier; +use crate::package::Package; +use crate::package::queue::PackageQueue; use crate::process::Process; +use crate::types::fetch::TryFetch; +use crate::util::prompts::prompt_bool; #[derive(Parser, Debug)] #[clap(name = "pkgr", version)] @@ -21,6 +26,8 @@ pub enum Command { Install { #[arg(short, long, default_value_t = false)] build: bool, + #[arg(short, long, default_value_t = false)] + ask: bool, package_identifier: PackageIdentifier, }, /// Remove a package from the system @@ -38,7 +45,11 @@ pub enum Command { }, /// Update packages on the system Update, - #[command(hide = true)] + /// Get info about a package + Info { + package_identifier: Option + }, + #[cfg(debug_assertions)] Debug, #[command(hide = true)] None, @@ -49,6 +60,7 @@ impl Command { match self { Command::Install { build, + ask, package_identifier, } => { warn!("Installer does not run in isolation."); @@ -59,8 +71,7 @@ impl Command { .unwrap(); info!("Parsing package..."); - trace!("Fetching package: {}", package_identifier); - let mut pkg = Package::fetch(package_identifier.clone()).unwrap(); + let mut pkg = Package::try_fetch(package_identifier.clone()).unwrap(); debug!("manifest size: {}kb", pkg.pkgfile.manifest.len() / 1024); debug!("files size: {}kb", pkg.pkgfile.data.len() / 1024); @@ -70,14 +81,19 @@ impl Command { exit(1); } - trace!("Starting install..."); - match pkg.install(CONFIG.with(|c| if !*build { c.build_by_default } else { *build })) { - Ok(_) => (), - Err(e) => { - error!("Install failed: {}", e.to_string()); - exit(1); + let mut queue = PackageQueue::new(); + queue.add_package(pkg, *build); + trace!("Installing queue..."); + { + if *ask { + info!("Install following packages?"); + info!(target: "item", "{}", queue); + if !prompt_bool("Continue?", false) { + return; + } } } + queue.install(*build); let end = std::time::Instant::now(); let _unix_end = std::time::SystemTime::now() @@ -87,7 +103,7 @@ impl Command { let duration = end.duration_since(start); info!("Install complete."); - info!("Install took {}ms.", duration.as_nanos() as f64 / 1000000.0); + info!("Install took {}.", humantime::format_duration(duration)); } Command::Remove { package_identifier, @@ -106,6 +122,74 @@ impl Command { Command::Update => { error!("Update is not yet implemented."); } + Command::Info { + package_identifier + } => { + if let Some(p) = package_identifier { + if let Ok(mut pkg) = Package::try_fetch(p.clone()) { + trace!("{}", pkg.pkgfile.manifest); + info!(target: "item", "Identifier: {:?}", pkg.identifier); + info!(target: "item", ""); + let mani = pkg.manifest(); + info!(target: "item", "Package name: {}", mani.package.name); + info!(target: "item", "Package description: {}", mani.package.description); + info!(target: "item", "Package version: {}", mani.package.version); + info!(target: "item", "Package tags: {}", mani.package.tags.join(", ")); + info!(target: "item", "Package type: {}", mani.package.package_type.to_string()); + info!(target: "item", ""); + info!(target: "item", "Supported install types: {}", { + let mut types = vec![]; + if let Some(_) = mani.bin { types.push("bin") } + if let Some(_) = mani.build { types.push("build") } + if let PackageType::Meta = mani.package.package_type { types.push("meta") } + types.join(", ") + }); + info!(target: "item", ""); + info!(target: "item", "Dependencies: {}", { + let deps = pkg.dependencies(); + if deps.len() == 0 { + String::from("None") + } else { + deps + .iter() + .map(|p| { + p.identifier.to_string() + }) + .collect::>() + .join(", ") + } + }); + info!(target: "item", "Build Dependencies: {}", { + let deps = pkg.build_dependencies(); + if deps.len() == 0 { + String::from("None") + } else { + deps + .iter() + .map(|p| { + p.identifier.to_string() + }) + .collect::>() + .join(", ") + } + }); + } else { + error!("Could not find {p}"); + } + } else { + info!("Welcome to pkgr!\n\ + {}\n\ + To get help please run \"{} -h\"", env!("CARGO_PKG_DESCRIPTION"), std::env::args().nth(0).unwrap()); + info!(""); + info!("version: {}", env!("CARGO_PKG_VERSION")); + info!("authors: {}", env!("CARGO_PKG_AUTHORS")); + + info!(""); + info!("If you can't seem to figure something out, use environment variable: PKGR_LOG_LEVEL=debug"); + trace!("{}", "The trace log level should really only be used by PKGR devs.".red()); + } + } + #[cfg(debug_assertions)] Command::Debug => { trace!("Trace message.\nWith newline."); debug!("Debug message.\nWith newline."); @@ -123,10 +207,6 @@ impl Command { .unwrap(); } - info!(""); - info!("PKGR VERSION: {}", env!("CARGO_PKG_VERSION")); - info!("PKGR AUTHORS: {}", env!("CARGO_PKG_AUTHORS")); - info!("PKGR DESCRIPTION: {}", env!("CARGO_PKG_DESCRIPTION")); info!(""); info!( "PKGR_LOG_LEVEL: {}", diff --git a/pkgr/src/config/mod.rs b/pkgr/src/config/mod.rs index 9a932d2..4c6876e 100644 --- a/pkgr/src/config/mod.rs +++ b/pkgr/src/config/mod.rs @@ -1,4 +1,8 @@ use serde::{Deserialize, Serialize}; +use storage::Storage; + +mod storage; +pub mod repos; #[derive(Debug, Serialize, Deserialize)] pub struct Config { @@ -6,13 +10,16 @@ pub struct Config { pub build_by_default: bool, #[serde(default)] pub tmp_dir: Option, + #[serde(default)] + pub storage: Storage } impl Default for Config { fn default() -> Config { Config { build_by_default: false, - tmp_dir: Some(String::from("/tmp/pkgr")) + tmp_dir: Some(String::from("/tmp/pkgr")), + storage: Storage::default() } } } diff --git a/pkgr/src/config/repos.rs b/pkgr/src/config/repos.rs new file mode 100644 index 0000000..8594a25 --- /dev/null +++ b/pkgr/src/config/repos.rs @@ -0,0 +1,37 @@ +use std::collections::HashMap; +use std::str::FromStr; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Clone, Serialize, Deserialize)] +pub struct Repo { + #[serde(default)] + pub name: String, + pub uri: Url +} + +impl Default for Repo { + fn default() -> Self { + Repo { + name: String::from("Repo"), + uri: Url::parse("tcp://0.0.0.0:0000").unwrap() + } + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct RepoFile { + pub repos: HashMap +} + +impl RepoFile { + pub fn from_path(path: &str) -> Result { + match std::fs::read_to_string(path) { + Ok(s) => match toml::from_str(&s) { + Ok(c) => Ok(c), + Err(e) => Err(format!("failed to parse config: {}", e)), + }, + Err(e) => Err(format!("failed to read config: {}", e)), + } + } +} \ No newline at end of file diff --git a/pkgr/src/config/storage.rs b/pkgr/src/config/storage.rs new file mode 100644 index 0000000..5fa8fa1 --- /dev/null +++ b/pkgr/src/config/storage.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Storage { + /// Where the repositories are defined. + #[serde(default)] + pub repo_file: String, + /// Where to store pkgs data + #[serde(default)] + pub data_dir: String, + /// Where to store repo indexes + #[serde(default)] + pub index_dir: String, +} + +impl Default for Storage { + fn default() -> Self { + Storage { + repo_file: String::from("/etc/pkgr.d/repos.toml"), + data_dir: String::from("/var/lib/pkgr/packages"), + index_dir: String::from("/var/lib/pkgr/indexes"), + } + } +} diff --git a/pkgr/src/logging.rs b/pkgr/src/logging.rs index 47a66bc..daef3eb 100644 --- a/pkgr/src/logging.rs +++ b/pkgr/src/logging.rs @@ -1,18 +1,19 @@ +use std::env; + use colored::Colorize; use fern::Dispatch; use log::{Record, SetLoggerError}; -use std::env; fn format_regular>(log: S, record: &Record) -> String { let log = log.into(); let line_prefix = |line: String, extend: bool| { let prefix = if extend { match record.level() { - log::Level::Trace => " :".bright_blue(), - log::Level::Debug => " :".green(), - log::Level::Info => " :".blue(), - log::Level::Warn => " :".yellow(), - log::Level::Error => " :".red(), + log::Level::Trace => " ]".bright_blue(), + log::Level::Debug => " ?".green(), + log::Level::Info => " >".blue(), + log::Level::Warn => " #".yellow(), + log::Level::Error => " !".red(), }.to_string() } else { match record.level() { @@ -43,14 +44,12 @@ pub fn setup_logger() -> Result<(), SetLoggerError> { Dispatch::new() .format(|out, message, record| { match record.metadata().target() { - "command:stdout" => { - out.finish(format_args!("{} {}", ">>".cyan(), message.to_string())); - return; - } - "command:stderr" => { - out.finish(format_args!("{} {}", ">>".red(), message.to_string())); - return; - } + // command output logging + "command:stdout" => out.finish(format_args!("{} {}", ">>".cyan(), message.to_string())), + "command:stderr" => out.finish(format_args!("{} {}", ">>".red(), message.to_string())), + // this target means, it's an item and not a log. + "item" => out.finish(format_args!("{} {}", "*".blue(), message.to_string())), + // default logging _ => out.finish(format_args!("{}", format_regular(message.to_string(), record))), } }) diff --git a/pkgr/src/main.rs b/pkgr/src/main.rs index 0191923..3aff896 100644 --- a/pkgr/src/main.rs +++ b/pkgr/src/main.rs @@ -1,16 +1,27 @@ -use crate::commands::Cli; - use clap::Parser; - use log::trace; +use crate::commands::Cli; +/// pkgr's commands. mod commands; +/// Logging implementations for pkgr. mod logging; +/// Package and helpers. mod package; +/// Repo and helpers +mod repo; +/// Process wrapper with logging wrapper. mod process; +/// tmpfs wrapper. mod tmpfs; +/// pkgr's optional config. mod config; +/// custom types used by pkgr +mod types; +/// utils +mod util; +mod api; thread_local! { static CONFIG: config::Config = config::Config::from_path("/etc/pkgr.toml") @@ -18,7 +29,8 @@ thread_local! { } fn main() { - logging::setup_logger().expect("Unable to setup logger."); + logging::setup_logger() + .expect("unable to set logger."); #[cfg(not(debug_assertions))] { diff --git a/pkgr/src/package/fetch.rs b/pkgr/src/package/fetch.rs deleted file mode 100644 index bc385ea..0000000 --- a/pkgr/src/package/fetch.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::package::identifier::PackageLocator; -use pkgfile::PKGFile; -use reqwest::blocking::get; -use std::error::Error; -use std::fmt::Display; -use std::io::Read; - -#[derive(Debug)] -pub enum FetchError { - HTTPError(reqwest::Error), - IOError(std::io::Error), - ParseError, -} - -impl Display for FetchError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - FetchError::HTTPError(e) => write!(f, "HTTP Error: {}", e), - FetchError::IOError(e) => write!(f, "IO Error: {}", e), - FetchError::ParseError => write!(f, "Parse Error"), - } - } -} - -impl Error for FetchError {} - -pub fn fetch_by_path>(path: S) -> Result { - std::fs::read(path.into()) - .map_err(|e| FetchError::IOError(e)) - .and_then(|bytes| PKGFile::try_from(bytes).map_err(|_| FetchError::ParseError)) -} - -pub fn fetch_by_uri>(uri: S) -> Result { - // get file contents as bytes - let mut bytes = Vec::new(); - match get(uri.into()) { - Ok(response) => { - match response.take(1024 * 1024).read_to_end(&mut bytes) { - Ok(_) => (), - Err(e) => return Err(FetchError::IOError(e)), - }; - } - Err(e) => return Err(FetchError::HTTPError(e)), - }; - // parse bytes as PKGFile - match PKGFile::try_from(bytes) { - Ok(pkgfile) => Ok(pkgfile), - Err(_e) => Err(FetchError::ParseError), - } -} - -pub fn fetch_by_package_locator(_package_locator: PackageLocator) -> Result { - // TODO: search index for package locator - Ok(PKGFile::default()) -} diff --git a/pkgr/src/package/identifier.rs b/pkgr/src/package/identifier.rs index 3fde5e7..e39ac87 100644 --- a/pkgr/src/package/identifier.rs +++ b/pkgr/src/package/identifier.rs @@ -1,7 +1,9 @@ use std::error::Error; +use std::fmt::Formatter; use std::str::FromStr; use regex::Regex; +use serde::Serializer; #[derive(Debug, Clone)] pub enum PackageIdentifierError { @@ -22,11 +24,29 @@ impl std::fmt::Display for PackageIdentifierError { impl Error for PackageIdentifierError {} -#[derive(Debug, Clone)] +#[derive(Clone, Eq, PartialEq)] pub enum PackageIdentifier { PackageLocator(PackageLocator), URI(String), - Path(String), + Path(String) +} + +impl std::fmt::Debug for PackageIdentifier { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if f.alternate() { + match self { + PackageIdentifier::PackageLocator(pl) => write!(f, "PackageLocator({:#?})", pl), + PackageIdentifier::URI(uri) => write!(f, "URI({:?})", uri), + PackageIdentifier::Path(path) => write!(f, "Path({:?})", path), + } + } else { + match self { + PackageIdentifier::PackageLocator(pl) => write!(f, "PL: {:?}", pl), + PackageIdentifier::URI(uri) => write!(f, "URI: {:?}", uri), + PackageIdentifier::Path(path) => write!(f, "Path: {:?}", path), + } + } + } } impl std::fmt::Display for PackageIdentifier { @@ -39,7 +59,7 @@ impl std::fmt::Display for PackageIdentifier { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct PackageLocator { pub name: String, pub version: Option, @@ -140,8 +160,9 @@ impl FromStr for PackageLocator { } } -impl From<(String, String)> for PackageLocator { - fn from((name, locate_str): (String, String)) -> Self { +impl TryFrom<(String, String)> for PackageLocator { + type Error = PackageIdentifierError; + fn try_from((name, locate_str): (String, String)) -> Result { // name = "pkg" // locate_str = "1.0.0:tag1,tag2" or "1.0.0" or "tag1,tag2" let mut version = None; @@ -165,10 +186,10 @@ impl From<(String, String)> for PackageLocator { ); } - PackageLocator { + Ok(PackageLocator { name, version, tags, - } + }) } } diff --git a/pkgr/src/package/installer/errors.rs b/pkgr/src/package/installer/errors.rs index 9e86f1b..332a06f 100644 --- a/pkgr/src/package/installer/errors.rs +++ b/pkgr/src/package/installer/errors.rs @@ -1,21 +1,26 @@ use std::fmt::Display; +use std::io; #[derive(Debug)] pub enum BinError { UnpackError(String), + IOError(io::Error), + Cancelled, } impl Display for BinError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { BinError::UnpackError(e) => write!(f, "Unpack error: {}", e), + BinError::IOError(e) => write!(f, "IO error: {}", e), + BinError::Cancelled => write!(f, "Cancelled by user"), } } } #[derive(Debug)] pub enum BuildError { - InvalidManifest, + InvalidManifest(String), } impl Display for BuildError { @@ -30,17 +35,15 @@ impl Display for BuildError { pub enum InstallError { BuildError(BuildError), BinError(BinError), - InvalidManifest, - Generic, + InvalidManifest(String), } impl ToString for InstallError { fn to_string(&self) -> String { match self { - InstallError::BuildError(e) => format!("Build error: \n{}", e), - InstallError::BinError(e) => format!("Bin error: \n{}", e), - InstallError::InvalidManifest => "Invalid manifest".to_string(), - InstallError::Generic => "Install error".to_string(), + InstallError::BuildError(e) => format!("{}", e), + InstallError::BinError(e) => format!("{}", e), + InstallError::InvalidManifest(s) => format!("{}", s), } } } diff --git a/pkgr/src/package/installer/mod.rs b/pkgr/src/package/installer/mod.rs index a28f849..4dbeab0 100644 --- a/pkgr/src/package/installer/mod.rs +++ b/pkgr/src/package/installer/mod.rs @@ -1,13 +1,17 @@ -use crate::tmpfs::TempDir; +use std::io; +use std::fs::remove_dir_all; +use std::path::Path; + +use log::{debug, trace}; + use errors::{BinError, BuildError, InstallError}; -use log::{debug, error, info, trace}; use manifest::Manifest; use pkgfile::PKGFile; - -use std::process::exit; use crate::CONFIG; -use crate::package::identifier::{PackageIdentifier, PackageLocator}; -use crate::package::Package; + +use crate::tmpfs::TempDir; +use crate::types::fetch::TryFetch; +use crate::util::fs::copy_recursively; pub mod errors; @@ -37,11 +41,14 @@ impl PackageInstaller { if !self.pkgfile.has_data() { return Err(BinError::UnpackError("package has no data".to_string())); } - if std::path::Path::new(&path).exists() { - return Err(BinError::UnpackError(format!( - "path already exists: {}", - path - ))); + let path = std::path::Path::new(&path); + if path.exists() { + trace!("cache already exists.."); + debug!("removing cache dir..."); + match remove_dir_all(path) { + Ok(_) => debug!("removed cache directory"), + Err(e) => return Err(BinError::UnpackError(format!("unable to remove directory ({}): {}", path.to_str().unwrap_or("unknown"), e.to_string()))) + } } tar::Archive::new(self.pkgfile.data.as_slice()) .unpack(&path) @@ -52,39 +59,32 @@ impl PackageInstaller { let mut tmpdir = TempDir::default(); tmpdir.push(&self.manifest.package.name); trace!("extracting package into: {}", tmpdir.to_string()); - match self.extract_to(tmpdir.to_string()) { - Ok(_) => {} - Err(e) => return Err(e), + if let Err(e) = self.extract_to(tmpdir.to_string()) { + return Err(e); } debug!("extracted package in: {}", tmpdir.to_string()); + match self.apply_overlay() { + Ok(_) => Ok(()), + Err(e) => Err(BinError::IOError(e)) + } + } + + fn apply_overlay(&self) -> Result<(), io::Error> { + let mut tmpdir = TempDir::default(); + tmpdir.push(&self.manifest.package.name); + tmpdir.push(&self.manifest.bin.clone().unwrap().root); + if let Err(e) = copy_recursively(&tmpdir.path(), Path::new("/")) { + return Err(e); + } Ok(()) } fn build(&self) -> Result<(), BuildError> { if let None = self.manifest.build.clone() { - return Err(BuildError::InvalidManifest); + return Err(BuildError::InvalidManifest(String::from("No build manifest"))); } let build_manifest = self.manifest.build.clone().unwrap(); // TODO: Check dependencies - for pkg_tuple in build_manifest.dependencies { - let mut pkg = Package::fetch( - PackageIdentifier::PackageLocator( - PackageLocator::from(pkg_tuple) - ) - ).expect("no pkg"); - - if !pkg.is_installed() { - match pkg.install(CONFIG.with(|c| { - c.build_by_default - })) { - Ok(_) => { info!("Installed dependency: \"{}\"", pkg.manifest().package.name) }, - Err(_) => { - error!("Could not install dependency: \"{}\"", pkg.identifier); - exit(1); - } - } - } - } Ok(()) } @@ -95,7 +95,7 @@ impl PackageInstaller { if let None = self.manifest.bin { self.install_type = InstallType::Build; if let None = self.manifest.build { - return Err(InstallError::InvalidManifest); + return Err(InstallError::InvalidManifest(String::from("no bin or build manifest"))); } } } @@ -103,17 +103,23 @@ impl PackageInstaller { if let None = self.manifest.build { self.install_type = InstallType::Bin; if let None = self.manifest.bin { - return Err(InstallError::InvalidManifest); + return Err(InstallError::InvalidManifest(String::from("no build or bin manifest"))); } } } } - match self.install_type { + let r = match self.install_type { InstallType::Bin => self.bin() .map_err(|e| InstallError::BinError(e)), InstallType::Build => self.build() .map_err(|e| InstallError::BuildError(e)), + }; + + if let Err(e) = r { + return Err(e); } + + Ok(()) } } diff --git a/pkgr/src/package/mod.rs b/pkgr/src/package/mod.rs index 79de18d..6378cef 100644 --- a/pkgr/src/package/mod.rs +++ b/pkgr/src/package/mod.rs @@ -1,19 +1,31 @@ -use log::{info, trace}; +use std::fmt::Display; +use std::path::Path; +use std::process::exit; + +use log::{debug, error, info, trace}; +use reqwest::blocking::get; + +use manifest::build::Build; +use pkgfile::PKGFile; + +use crate::package::identifier::{PackageIdentifier, PackageLocator}; +use crate::types::fetch::TryFetch; -pub mod fetch; pub mod identifier; pub mod installer; +pub mod queue; +#[derive(Eq, PartialEq)] pub struct Package { - pub identifier: identifier::PackageIdentifier, - pub pkgfile: pkgfile::PKGFile, + pub identifier: PackageIdentifier, + pub pkgfile: PKGFile, is_installed: bool, is_indexed: bool, } impl Package { /// Create a new package from a package identifier and a package file. - pub fn new(identifier: identifier::PackageIdentifier, pkgfile: pkgfile::PKGFile) -> Package { + pub fn new(identifier: PackageIdentifier, pkgfile: PKGFile) -> Package { Package { identifier, pkgfile, @@ -22,34 +34,60 @@ impl Package { } } - /// Fetch a package from a package identifier. - pub fn fetch( - package_identifier: identifier::PackageIdentifier, - ) -> Result { - match &package_identifier { - identifier::PackageIdentifier::Path(path) => { - trace!("fetching package from path: {}", path); - let pkgfile = fetch::fetch_by_path(path).unwrap(); - Ok(Package::new(package_identifier, pkgfile)) - } - identifier::PackageIdentifier::URI(url) => { - trace!("fetching package from uri: {}", url); - let pkgfile = fetch::fetch_by_uri(url).unwrap(); - Ok(Package::new(package_identifier, pkgfile)) - } - identifier::PackageIdentifier::PackageLocator(locator) => { - trace!("fetching package from locator: {}", locator); - let pkgfile = fetch::fetch_by_package_locator(locator.clone()).unwrap(); - Ok(Package::new(package_identifier, pkgfile)) - } - } - } - /// Get the package manifest. pub fn manifest(&self) -> manifest::Manifest { manifest::Manifest::try_from(self.pkgfile.manifest.clone()).unwrap() } + /// Get package dependencies + pub fn dependencies(&self) -> Vec { + let mut dependencies = vec![]; + for dependency in self.manifest().dependencies { + let pkglocate = if let Ok(pl) = PackageLocator::try_from(dependency.clone()) { + trace!("parsed pl successfully..."); + pl + } else { + error!("Could not parse package locator: {:?} in dependencies.", &dependency); + exit(1); + }; + let pkg = match Package::try_fetch(PackageIdentifier::PackageLocator(pkglocate)) { + Ok(p) => p, + Err(e) => { + error!("Could not fetch dependency: {}", e); + exit(1); + } + }; + dependencies.push(pkg); + } + dependencies + } + + pub fn build_dependencies(&self) -> Vec { + let mut dependencies = vec![]; + for dependency in self + .manifest() + .build + .unwrap_or(Build::default()) + .dependencies { + let pkglocate = if let Ok(pl) = PackageLocator::try_from(dependency.clone()) { + trace!("parsed pl successfully..."); + pl + } else { + error!("Could not parse package locator: {:?} in dependencies.", &dependency); + exit(1); + }; + let pkg = match Package::try_fetch(PackageIdentifier::PackageLocator(pkglocate)) { + Ok(p) => p, + Err(e) => { + error!("Could not fetch dependency: {}", e); + exit(1); + } + }; + dependencies.push(pkg); + } + dependencies + } + /// Install the package. pub fn install(&mut self, build: bool) -> Result<(), installer::errors::InstallError> { let manifest = self.manifest(); @@ -96,3 +134,57 @@ impl Package { unimplemented!(); } } + +#[derive(Debug)] +pub enum FetchError { + HTTPError(reqwest::Error), + IOError(std::io::Error), + ParseError, +} + +impl Display for FetchError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + FetchError::HTTPError(e) => write!(f, "HTTP Error: {}", e), + FetchError::IOError(e) => write!(f, "IO Error: {}", e), + FetchError::ParseError => write!(f, "Parse Error"), + } + } +} + +impl TryFetch for Package { + type Error = FetchError; + + /// Fetch a package from a package identifier. + fn try_fetch(query: PackageIdentifier) -> Result { + trace!("Fetching: {query:#?}"); + let pkgfile = match &query { + PackageIdentifier::Path(s) => match PKGFile::try_from(Path::new(&s)) { + Ok(p) => Ok(p), + Err(e) => Err(FetchError::ParseError) + }, + PackageIdentifier::URI(s) => { + let mut bytes = Vec::new(); + debug!("sending GET request..."); + match get::(s.into()) { + Ok(response) => { + debug!("Got response!"); + if let Ok(b) = response.bytes() { + bytes.extend(b); + } + } + Err(e) => return Err(FetchError::HTTPError(e)), + }; + // parse bytes as PKGFile + match PKGFile::try_from(bytes) { + Ok(pkgfile) => Ok(pkgfile), + Err(_e) => Err(FetchError::ParseError), + } + } + PackageIdentifier::PackageLocator(l) => unimplemented!() + }; + + pkgfile + .map(|p| Package::new(query, p)) + } +} \ No newline at end of file diff --git a/pkgr/src/package/queue.rs b/pkgr/src/package/queue.rs new file mode 100644 index 0000000..601d8da --- /dev/null +++ b/pkgr/src/package/queue.rs @@ -0,0 +1,51 @@ +use std::fmt::{Display, Formatter}; + +use log::trace; + +use crate::package::Package; + +pub struct PackageQueue { + packages: Vec, +} + +impl PackageQueue { + pub fn new() -> Self { + PackageQueue { + packages: vec![] + } + } + + pub fn add_package(&mut self, package: Package, build: bool) { + let dependencies = package.dependencies(); + for dependency in dependencies { + trace!("Checking package: {}", &dependency.identifier); + if self.packages.contains(&dependency) { + continue; + } + trace!("Adding package: {}", &dependency.identifier); + self.packages.push(dependency); + } + if !self.packages.contains(&package) { + self.packages.push(package); + } + } + + pub fn install(&mut self, build: bool) { + self.packages + .iter_mut() + .for_each(|pkg| { + pkg.install(build).expect("TODO: panic message"); + }); + } +} + +impl Display for PackageQueue { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for pkg in &self.packages { + if let Err(e) = write!(f, "{}", pkg.identifier) { + return Err(e); + } + } + write!(f, "") + } +} \ No newline at end of file diff --git a/pkgr/src/process.rs b/pkgr/src/process.rs index f6f4e21..fbaf567 100644 --- a/pkgr/src/process.rs +++ b/pkgr/src/process.rs @@ -1,7 +1,7 @@ use std::io::{BufRead, BufReader}; use std::process::Command; -use log::{info, trace}; +use log::{info, trace}; pub struct Process { pub command: Vec, diff --git a/pkgr/src/repo/index/index_package.rs b/pkgr/src/repo/index/index_package.rs new file mode 100644 index 0000000..ca60160 --- /dev/null +++ b/pkgr/src/repo/index/index_package.rs @@ -0,0 +1,18 @@ +use std::io; +use std::path::Path; +use serde::{Deserialize, Serialize}; +use url::Url; +use pkgfile::PKGFile; +use crate::api::Query; +use crate::CONFIG; +use crate::package::Package; +use crate::repo::index::RepoIndex; + +/// This struct solely exists for indexing and has no real functionality. +#[derive(Serialize, Deserialize)] +pub struct IndexPackage { + name: String, + versions: Vec, + tags: Vec, + uri: Url +} \ No newline at end of file diff --git a/pkgr/src/repo/index/mod.rs b/pkgr/src/repo/index/mod.rs new file mode 100644 index 0000000..fc516b6 --- /dev/null +++ b/pkgr/src/repo/index/mod.rs @@ -0,0 +1,58 @@ +use std::collections::HashMap; +use std::io; +use std::path::Path; +use serde::{Deserialize, Serialize}; +use crate::api::Request; +use crate::CONFIG; +use crate::repo::index::index_package::IndexPackage; +use crate::repo::Repo; +use crate::types::fetch::{Fetch, TryFetch}; +use crate::util::create_uuid; + +pub mod index_package; + +#[derive(Serialize, Deserialize)] +pub struct RepoIndex { + origin_repo: String, + packages: HashMap +} + +impl RepoIndex { + // /// Fetch existing index or create a new one. + // pub fn from_repo(repo: Repo) -> Self { + // + // } + // + // /// Create new index. + // pub fn create_with_repo(repo: Repo) -> Self { + // } + + /// Get repo + pub fn get_repo(&self) -> io::Result { + Ok(Repo::from_name(&self.origin_repo)?) + } + + pub fn from_path(path: &str) -> Result { + match std::fs::read_to_string(path) { + Ok(s) => match toml::from_str(&s) { + Ok(c) => Ok(c), + Err(e) => Err(format!("failed to parse config: {}", e)), + }, + Err(e) => Err(format!("failed to read config: {}", e)), + } + } +} + +impl TryFetch for RepoIndex { + type Error = (); + /// Fetch + fn try_fetch(query: Repo) -> Result { + let path = CONFIG.with(|c| c.storage.index_dir.clone()) + query.uri.as_str() + ".toml"; + if Path::new(path.as_str()).exists() { + RepoIndex::from_path(path.as_str()) + .map_err(|_| ()) + } else { + Err(()) + } + } +} \ No newline at end of file diff --git a/pkgr/src/repo/mod.rs b/pkgr/src/repo/mod.rs new file mode 100644 index 0000000..d0c3e88 --- /dev/null +++ b/pkgr/src/repo/mod.rs @@ -0,0 +1,42 @@ +use std::io; +use std::io::{Error, ErrorKind}; +use std::path::Path; +use url::Url; +use crate::CONFIG; +use crate::config::repos::RepoFile; + +/// Indexed repos +pub mod index; + +pub struct Repo { + name: String, + uri: Url, +} + +impl Repo { + pub fn from_name(name: &String) -> io::Result { + let r = RepoFile::from_path(CONFIG.with(|c| c.storage.repo_file.clone()).as_str()) + .map_err(|e| Error::new(ErrorKind::Other, e))?; + let r = r + .repos + .get(name) + .ok_or(Error::new(ErrorKind::InvalidData, "Could not get repo"))?; + Ok(Repo { + name: r.name.clone(), + uri: r.uri.clone() + }) + } + + pub fn get_name(&self) -> String { + self.name.clone() + } + + pub fn get_uri(&self) -> Url { + self.uri.clone() + } + + /// Fetch indexed repo + pub fn get_index(&self) { + + } +} \ No newline at end of file diff --git a/pkgr/src/tmpfs.rs b/pkgr/src/tmpfs.rs index d21aff7..7d68892 100644 --- a/pkgr/src/tmpfs.rs +++ b/pkgr/src/tmpfs.rs @@ -1,20 +1,32 @@ use std::path::PathBuf; + use crate::CONFIG; +#[derive(Clone)] pub struct TempDir { path: PathBuf, } impl TempDir { pub fn new(path: PathBuf) -> TempDir { + let pbs: String = path.to_str().unwrap().into(); + let path = expanduser::expanduser(&pbs).unwrap(); if !path.exists() { std::fs::create_dir_all(&path).unwrap(); } TempDir { path } } + pub fn path(&self) -> PathBuf { + self.path.clone() + } + pub fn push>(&mut self, path: S) { - self.path.push(path.into()); + let mut path_str = path.into(); + if path_str.starts_with('/') { + path_str = path_str[1..].to_string(); + } + self.path.push(path_str); } } diff --git a/pkgr/src/types/fetch.rs b/pkgr/src/types/fetch.rs new file mode 100644 index 0000000..d3b7eac --- /dev/null +++ b/pkgr/src/types/fetch.rs @@ -0,0 +1,10 @@ +/// Get a result from an external source +pub trait Fetch { + fn fetch(query: Q) -> R; +} + +/// Try to get a result from an external source +pub trait TryFetch { + type Error; + fn try_fetch(query: Q) -> Result; +} \ No newline at end of file diff --git a/pkgr/src/types/mod.rs b/pkgr/src/types/mod.rs new file mode 100644 index 0000000..c798af5 --- /dev/null +++ b/pkgr/src/types/mod.rs @@ -0,0 +1 @@ +pub mod fetch; \ No newline at end of file diff --git a/pkgr/src/util/fs.rs b/pkgr/src/util/fs.rs new file mode 100644 index 0000000..815b950 --- /dev/null +++ b/pkgr/src/util/fs.rs @@ -0,0 +1,41 @@ +use std::{fs, io}; +use std::fs::DirEntry; +use std::path::Path; + +use log::trace; + +pub fn visit_dirs(dir: &Path, cb: &dyn Fn(&DirEntry)) -> std::io::Result<()> { + if dir.is_dir() { + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + visit_dirs(&path, cb)?; + } else { + cb(&entry); + } + } + } + Ok(()) +} + +pub fn copy_recursively(source: &Path, target: &Path) -> io::Result<()> { + if source.is_file() { + trace!("source: {:?}, target: {:?}", source, target); + fs::copy(source, target)?; + } else if source.is_dir() { + if !target.exists() { + fs::create_dir(target)?; + } + + for entry in fs::read_dir(source)? { + let entry = entry?; + let file_name = entry.file_name(); + let source_path = entry.path(); + let target_path = target.join(&file_name); + + copy_recursively(&source_path, &target_path)?; + } + } + Ok(()) +} \ No newline at end of file diff --git a/pkgr/src/util/mod.rs b/pkgr/src/util/mod.rs new file mode 100644 index 0000000..25c313c --- /dev/null +++ b/pkgr/src/util/mod.rs @@ -0,0 +1,9 @@ +pub mod prompts; +/// Helpers for fs +pub mod fs; + +/// Create a UUID +pub fn create_uuid() -> String { + // TODO + String::from("rand") +} \ No newline at end of file diff --git a/pkgr/src/util/prompts.rs b/pkgr/src/util/prompts.rs new file mode 100644 index 0000000..9dcae5d --- /dev/null +++ b/pkgr/src/util/prompts.rs @@ -0,0 +1,33 @@ +use std::io::Write; + +use log::trace; + +pub fn is_noninteractive() -> bool { + if let Ok(v) = std::env::var("PKGR_NON_INTERACTIVE") { + trace!("PKGR_NON_INTERACTIVE={}", v); + match v.as_str() { + "1" => true, + "true" => true, + _ => false + } + } else { false } +} + +pub fn prompt_bool>(prompt: S, default: bool) -> bool { + if is_noninteractive() { return default; } + print!("{} [{}]: ", prompt.into(), if default { "Y/n" } else { "y/N" }); + match std::io::stdout().flush() { + Ok(_) => (), + _ => println!() + }; + + let mut input = String::new(); + std::io::stdin().read_line(&mut input).expect("Failed to read input."); + let answer = input.trim().to_lowercase(); + + if answer.is_empty() { + default + } else { + answer == "y" || answer == "yes" + } +} \ No newline at end of file