feat: added command functionality to install

This commit is contained in:
Strix 2023-10-14 22:39:43 +02:00
parent 3a21d12c07
commit 092c616ca4
No known key found for this signature in database
GPG key ID: 49B2E37B8915B774
20 changed files with 464 additions and 76 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 didier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -5,12 +5,12 @@ use toml;
use manifest::{self, Manifest};
#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PKGR {
pub bootstrap: Option<Bootstrap>,
}
#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bootstrap {
pub check_installed_commands: Vec<String>,
pub commands: Vec<String>,

View file

@ -11,6 +11,9 @@ To find the latest we'll use the `latest` symlink:
install_start = "2021-01-01T00:00:00Z" # install_start is the time the pkgr started installing the package
install_end = "2021-01-01T00:00:00Z" # install_end is the time the pkgr finished installing the package
install_by = "pkgr" # install_by is the name of the package manager that installed the package
package_identifier = "uri"
package_id = "https://..."
[old_versions] # This logs old versions of the package (that were installed before)
9 = "../9/pkgr.pkg"

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bin {
pub root: String,
pub checksums: HashMap<String, String>,

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Build {
build_script: String,
install_script: String,

View file

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FS {
pub config: Option<String>,
pub data: Option<String>,

View file

@ -8,9 +8,9 @@ pub mod fs;
pub mod package;
pub mod pkgr;
#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Manifest<P = Option<pkgr::PKGR>> {
pub struct Manifest<P: Clone = Option<pkgr::PKGR>> {
pub package: package::Package,
pub dependencies: HashMap<String, String>,
pub fs: fs::FS,
@ -19,7 +19,7 @@ pub struct Manifest<P = Option<pkgr::PKGR>> {
pub pkgr: P,
}
impl<T> Manifest<T> {
impl<P: Clone> Manifest<P> {
pub fn valid(&self) -> bool {
if self.bin.is_none() && self.build.is_none() {
return false;
@ -28,6 +28,13 @@ impl<T> Manifest<T> {
}
}
impl TryFrom<String> for Manifest {
type Error = toml::de::Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
toml::from_str(&s)
}
}
impl FromStr for Manifest {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -36,7 +43,7 @@ impl FromStr for Manifest {
}
impl Default for Manifest {
fn default() -> Self {
fn default() -> Manifest {
Manifest {
package: package::Package::default(),
dependencies: HashMap::default(),

View file

@ -2,7 +2,7 @@ use std::str::FromStr;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PackageType {
#[serde(rename = "application")]
Application,
@ -12,7 +12,7 @@ pub enum PackageType {
Meta,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Package {
pub name: String,

View file

@ -1,4 +1,11 @@
use std::fmt::{Display, Formatter};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct PKGR {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PKGR {}
impl Display for PKGR {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "")
}
}

107
package.example.toml Normal file
View file

@ -0,0 +1,107 @@
#* = required.
[package]
name = "packager" #*
description = "A package installation tool" #*
## package.version
# needs to be an int and be incremented every time
# if you use a custom version system and want to use that, you can use the tag system!
# the highest package.version on the server will be given the tag "latest"
version = 1 # this can automatically be incremented when publishing by running `pkgr publish -i ...`
## package.tags
# you can add tags to a package to specify kinds
# there are some special tags:
# - latest
# automatically set on the last published version
# previous version will lose this tag
# - oldest
# assigned to the first version.
tags = [ "prod", "pkgr-spec-1" ]
## package.type
# Supported types:
# - application
# this type specifies that this package is an application.
# - library
# this type specifies that this package is a library.
# - meta
# this type specifies that this package does not install anything upon the system except for dependencies.
type = "application"
arch = "x86_64" # this is automatically filled by `pkgr publish ...`
## dependencies
# you may use the following syntax
# "<version/tag>" or "<version/tag>,<version/tag>,..."
[dependencies]
#example = { arch = "x86_64", tags = [ "stable" ], version = "v1" }
exnmbr2 = "v1,stable"
## fs
# specify some directories for ease of use
[fs]
## fs.config
# specifiy the config path
config = "/etc/packager"
## fs.data
# specify the data path
data = "/var/lib/packager"
## replace "pacakger" with your package name to find the default value.
## 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"
]

View file

@ -1,33 +1,8 @@
#* = required.
[package]
name = "packager" #*
description = "A package installation tool" #*
## package.version
# needs to be an int and be incremented every time
# if you use a custom version system and want to use that, you can use the tag system!
# the highest package.version on the server will be given the tag "latest"
version = 1 # this can automatically be incremented when publishing by running `pkgr publish -i ...`
## package.tags
# you can add tags to a package to specify kinds
# there are some special tags:
# - latest
# automatically set on the last published version
# previous version will lose this tag
# - oldest
# assigned to the first version.
tags = [ "prod", "pkgr-spec-1" ]
## package.type
# Supported types:
# - application
# this type specifies that this package is an application.
# - library
# this type specifies that this package is a library.
# - meta
# this type specifies that this package does not install anything upon the system except for dependencies.
type = "application"
arch = "x86_64" # this is automatically filled by `pkgr publish ...`
@ -36,19 +11,6 @@ arch = "x86_64" # this is automatically filled by `pkgr publish ...`
# you may use the following syntax
# "<version/tag>" or "<version/tag>,<version/tag>,..."
[dependencies]
#example = { arch = "x86_64", tags = [ "stable" ], version = "v1" }
exnmbr2 = "v1,stable"
## fs
# specify some directories for ease of use
[fs]
## fs.config
# specifiy the config path
config = "/etc/packager"
## fs.data
# specify the data path
data = "/var/lib/packager"
## replace "pacakger" with your package name to find the default value.
## bin
# Used for systems that don't want to build pkgs.

View file

@ -1,8 +1,24 @@
#[derive(Debug)]
pub struct PKGFile {
pub manifest: String,
pub data: Vec<u8>,
}
impl PKGFile {
pub fn new(manifest: String, data: Vec<u8>) -> PKGFile {
PKGFile { manifest, data }
}
}
impl Default for PKGFile {
fn default() -> PKGFile {
PKGFile {
manifest: String::new(),
data: Vec::new(),
}
}
}
impl TryFrom<Vec<u8>> for PKGFile {
type Error = ();

View file

@ -1,14 +1,25 @@
[package]
name = "pkgr"
description = "A package manager and build tool."
version = "0.1.0"
edition = "2021"
authors = [
"Didier <dev@faulty.nl>"
]
license-file = "../LICENSE"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
manifest = { path = "../manifest" }
pkgfile = { path = "../pkgfile" }
fern = "0.6.2"
log = "0.4.19"
regex = "1.9.1"
manifest = { path = "../manifest" }
clap = { version = "4.3.12", features = ["derive"] }
colored = "2.0.4"
toml = "0.7.6"
serde = { version = "1.0.171", features = ["derive"] }
libc = "0.2.80"
reqwest = { version = "0.11.18", features = ["blocking"] }
tar = "0.4.39"

View file

@ -1,6 +1,10 @@
use std::process::exit;
use crate::package::identifier::PackageIdentifier;
use clap::{Parser, Subcommand};
use log::error;
use log::{debug, error, info, trace, warn};
use manifest::Manifest;
use crate::package::builder::{InstallType, PackageInstaller};
use crate::package::fetch::fetch_package;
#[derive(Parser, Debug)]
#[clap(name = "pkgr", version)]
@ -33,6 +37,8 @@ pub enum Command {
/// Update packages on the system
Update,
#[command(hide = true)]
Debug,
#[command(hide = true)]
None,
}
@ -41,20 +47,47 @@ impl Command {
match self {
Command::Install {
build,
package_identifier: _,
package_identifier,
} => {
if *build {
error!("Build is not yet implemented.");
} else {
error!("Install is not yet implemented.");
warn!("Installer does not run in isolation.");
let start = std::time::Instant::now();
let unix_start = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap();
trace!("Fetching package: {}", package_identifier);
let pkgfile = fetch_package(package_identifier.clone()).unwrap();
debug!("size: manifest({}kb) data({}kb)", pkgfile.manifest.len() / 1024, pkgfile.data.len() / 1024);
trace!("parsing manifest");
let manifest = Manifest::try_from(pkgfile.manifest.clone()).unwrap();
debug!("manifest pkg name: {}", manifest.package.name);
trace!("creating installer");
let installer = PackageInstaller::new(manifest.clone(), pkgfile, if *build { InstallType::Build } else { InstallType::Bin });
trace!("starting install");
match installer.install() {
Ok(_) => {
info!("Sucessfully installed: {}", &manifest.package.name);
()
},
Err(e) => {
error!("{}", e.to_string())
}
}
let unix_end = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap();
let end = std::time::Instant::now();
// float ms
let duration = (end - start).as_nanos() as f64;
info!("Install took: {}ms", (duration / 1_000_000.0));
}
Command::Remove { package_identifier: _, index } => {
if *index {
error!("Index removal is not yet implemented.");
} else {
error!("Remove is not yet implemented.");
Command::Remove { package_identifier, index } => {
if let PackageIdentifier::URI(_) = package_identifier {
error!("URI is unsupported when removing applications.");
exit(1);
}
info!("Index: {}", index);
info!("Package identifier: {}", package_identifier);
}
Command::List { installed: _ } => {
error!("List is not yet implemented.");
@ -62,6 +95,19 @@ impl Command {
Command::Update => {
error!("Update is not yet implemented.");
}
Command::Debug => {
trace!("Trace message.");
debug!("Debug message.");
info!("Info message.");
warn!("Warning message.");
error!("Error message.");
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: {}", std::env::var("PKGR_LOG_LEVEL").unwrap_or_else(|_| "info".to_string()));
}
Command::None => {
error!("No command was specified.");
}

View file

@ -1,25 +1,35 @@
use clap::Parser;
use colored::Colorize;
use crate::commands::Cli;
use log::{debug, info, SetLoggerError, trace};
use log::{SetLoggerError, trace};
use std::env;
mod commands;
mod package;
mod tmp;
fn main() {
setup_logger().expect("Unable to setup logger.");
#[cfg(not(debug_assertions))]
{
#[cfg(target_family = "unix")]
if unsafe { libc::getuid() } != 0 {
use log::error;
error!("pkgr must be run as root.");
std::process::exit(1);
}
}
trace!("Parsing command line arguments...");
let c = Cli::parse();
debug!("Command line arguments parsed.");
trace!("Command line arguments: {:?}", c);
trace!("Executing command...");
c.command.execute();
debug!("Command executed.");
trace!("Command executed.");
trace!("Exiting...");
info!("Done.");
}
fn setup_logger() -> Result<(), SetLoggerError> {
@ -27,16 +37,30 @@ fn setup_logger() -> Result<(), SetLoggerError> {
.format(|out, message, record| {
out.finish(format_args!(
"{} {}",
match record.level().to_string().chars().nth(0).unwrap_or('I') {
'T' => "##".cyan(),
'D' => "::".yellow(),
'E' | 'W' => "!!".red(),
_ => "**".blue(),
},
// Some logic so messages look nice
if message.to_string().len() > 0 {
match record
.level()
.to_string()
.chars()
.nth(0)
.unwrap_or('T')
{
'T' => "[TRACE]".cyan(),
'D' => "??".green(),
'I' => "=>".blue(),
'W' => "##".yellow(),
'E' => "!!".red(),
_ => "**".blue(),
}.to_string()
} else { "".to_string() },
message.to_string().bright_white()
))
})
.level(env::var("PKGR_LOG_LEVEL").unwrap_or_else(|_| "info".to_string()).parse().unwrap_or(log::LevelFilter::Info))
.level(env::var("PKGR_LOG_LEVEL")
.unwrap_or_else(|_| "info".to_string())
.parse()
.unwrap_or(log::LevelFilter::Info))
.chain(std::io::stdout())
.apply()
}

View file

@ -0,0 +1,71 @@
use libc::fork;
use log::{debug, trace};
use manifest::Manifest;
use pkgfile::PKGFile;
use crate::tmp::TempDir;
#[derive(Debug)]
pub enum InstallType {
Build,
Bin
}
#[derive(Debug)]
pub enum InstallError {
BuildError(String),
BinError(String),
InstallError
}
impl ToString for InstallError {
fn to_string(&self) -> String {
match self {
InstallError::BuildError(e) => format!("Build error: {}", e),
InstallError::BinError(e) => format!("Bin error: {}", e),
InstallError::InstallError => "Install error".to_string(),
}
}
}
#[derive(Debug)]
pub struct PackageInstaller {
manifest: Manifest,
pkgfile: PKGFile,
install_type: InstallType
}
impl PackageInstaller {
pub fn new(m: Manifest, p: PKGFile, i: InstallType) -> PackageInstaller {
PackageInstaller {
manifest: m,
pkgfile: p,
install_type: i
}
}
fn extract_to<S: Into<String>>(&self, path: S) -> Result<(), String> {
tar::Archive::new(self.pkgfile.data.as_slice())
.unpack(path.into())
.map_err(|e| e.to_string())
}
fn bin(&self) -> Result<(), String> {
let mut tmpdir = TempDir::default();
tmpdir.push(&self.manifest.package.name);
trace!("extracting package into: {}", tmpdir.to_string());
self.extract_to(tmpdir.to_string())?;
debug!("extracted package in: {}", tmpdir.to_string());
Ok(())
}
fn build(&self) -> Result<(), String> {
Ok(())
}
pub fn install(&self) -> Result<(), InstallError> {
match self.install_type {
InstallType::Bin => self.bin().map_err(|e| InstallError::BinError(e)),
InstallType::Build => self.build().map_err(|e| InstallError::BuildError(e))
}
}
}

59
pkgr/src/package/fetch.rs Normal file
View file

@ -0,0 +1,59 @@
use std::error::Error;
use std::fmt::Display;
use std::fs::File;
use std::io::Read;
use log::warn;
use pkgfile::PKGFile;
use crate::package::identifier::PackageIdentifier;
use reqwest::blocking::get;
#[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_package(package_identifier: PackageIdentifier) -> Result<PKGFile, FetchError> {
match package_identifier {
PackageIdentifier::Path(path) => {
std::fs::read(path)
.map_err(|e| FetchError::IOError(e)).and_then(|bytes| {
PKGFile::try_from(bytes).map_err(|_| FetchError::ParseError)
})
}
PackageIdentifier::URI(uri) => {
// get file contents as bytes
let mut bytes = Vec::new();
match get(&uri) {
Ok(mut 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),
}
}
PackageIdentifier::PackageLocator(package_locator) => {
Ok(PKGFile::default())
}
}
}

View file

@ -26,6 +26,7 @@ impl Error for PackageIdentifierError {}
pub enum PackageIdentifier {
PackageLocator(PackageLocator),
URI(String),
Path(String),
}
impl std::fmt::Display for PackageIdentifier {
@ -33,6 +34,7 @@ impl std::fmt::Display for PackageIdentifier {
match self {
PackageIdentifier::PackageLocator(pl) => write!(f, "{}", pl),
PackageIdentifier::URI(uri) => write!(f, "{}", uri),
PackageIdentifier::Path(path) => write!(f, "{}", path),
}
}
}
@ -61,7 +63,7 @@ impl FromStr for PackageIdentifier {
type Err = PackageIdentifierError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let uri_re = regex::Regex::new(r"^[a-zA-Z0-9]+://").unwrap();
let uri_re = Regex::new(r"^[a-zA-Z0-9]+://").unwrap();
if uri_re.is_match(s) {
// there needs to be stuff after the protocol
let split = s.split("://").collect::<Vec<&str>>();
@ -69,6 +71,8 @@ impl FromStr for PackageIdentifier {
return Err(PackageIdentifierError::InvalidURI(s.to_string()));
}
Ok(PackageIdentifier::URI(s.to_string()))
} else if std::path::Path::new(s).exists() {
return Ok(PackageIdentifier::Path(s.to_string()));
} else {
let pl = match PackageLocator::from_str(s) {
Ok(pl) => pl,
@ -84,7 +88,7 @@ impl FromStr for PackageLocator {
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[allow(unused_assignments)] // false positive
let mut name = None;
let mut name = None;
let mut version = None;
let mut tags = None;

View file

@ -1 +1,3 @@
pub mod identifier;
pub mod builder;
pub mod fetch;

48
pkgr/src/tmp.rs Normal file
View file

@ -0,0 +1,48 @@
use std::path::PathBuf;
pub struct TempDir {
path: PathBuf,
}
impl TempDir {
pub fn new(path: PathBuf) -> TempDir {
if !path.exists() {
std::fs::create_dir_all(&path).unwrap();
}
TempDir {
path
}
}
pub fn push<S: Into<String>>(&mut self, path: S) {
self.path.push(path.into());
}
}
impl Default for TempDir {
fn default() -> TempDir {
TempDir::new({
let mut t = std::env::temp_dir();
t.push("pkgr");
t
})
}
}
impl Into<PathBuf> for TempDir {
fn into(self) -> PathBuf {
self.path
}
}
impl Into<String> for TempDir {
fn into(self) -> String {
self.path.to_str().unwrap().to_string()
}
}
impl ToString for TempDir {
fn to_string(&self) -> String {
self.path.to_str().unwrap().to_string()
}
}