Compare commits

..

No commits in common. "v0.1.0-alpha4" and "main" have entirely different histories.

30 changed files with 424 additions and 100 deletions

25
.idea/jsonSchemas.xml Normal file
View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JsonSchemaMappingsProjectConfiguration">
<state>
<map>
<entry key="Woodpecker pipeline config">
<value>
<SchemaInfo>
<option name="name" value="Woodpecker pipeline config" />
<option name="relativePathToSchema" value="https://raw.githubusercontent.com/woodpecker-ci/woodpecker/master/pipeline/schema/schema.json" />
<option name="applicationDefined" value="true" />
<option name="patterns">
<list>
<Item>
<option name="path" value=".woodpecker.yml" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
</map>
</state>
</component>
</project>

View file

@ -5,6 +5,43 @@
> "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 <package_toml_path> <output_path> [<directories_to_include> ...]
# 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 :)

View file

@ -30,12 +30,12 @@ pub fn mani_from_str(s: &str) -> Manifest<Option<PKGR>> {
fs: mani.fs,
bin: mani.bin,
build: mani.build,
pkgr: bmani.pkgr,
ext: bmani.pkgr,
}
}
pub fn run_bootstrap(mani: Manifest<Option<PKGR>>) -> bool {
if let Some(pkgr) = mani.pkgr {
if let Some(pkgr) = mani.ext {
if let Some(bootstrap) = pkgr.bootstrap {
fn run_command<S: Into<String>>(s: S) -> i32 {
std::process::Command::new("sh")

2
build
View file

@ -3,7 +3,7 @@
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/YOU-CAN-NOT-USE-PKGR-TO-UPDATE-PKGR.txt
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

View file

@ -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

View file

@ -7,7 +7,7 @@ PKGFiles are an all-in-one solution to install a package.
## Overhead
Because pkgfiles include everything, many times you can download more than what is needed.
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

View file

@ -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, "")
}

View file

@ -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<P: Clone = Option<pkgr::PKGR>> {
pub struct Manifest<E: Clone = Option<ext::Extension>> {
pub package: package::Package,
pub dependencies: HashMap<String, String>,
pub fs: fs::FS,
pub bin: Option<bin::Bin>,
pub build: Option<build::Build>,
pub pkgr: P,
pub ext: E,
}
impl<P: Clone> Manifest<P> {
@ -50,7 +50,7 @@ impl Default for Manifest {
fs: fs::FS::default(),
bin: None,
build: None,
pkgr: None,
ext: None,
}
}
}

View file

@ -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"

View file

@ -25,3 +25,5 @@ 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"

View file

@ -1,66 +1,32 @@
[package]
name = "packager" #*
description = "A package installation tool" #*
version = 1 # this can automatically be incremented when publishing by running `pkgr publish -i ...`
name = "packager"
description = "A package installation tool"
version = 1
tags = [ "prod", "pkgr-spec-1" ]
type = "application"
arch = "x86_64"
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]
## bin
# Used for systems that don't want to build pkgs.
[bin] # binary files root
[bin]
root = "/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_script = "scripts/build"
install_script = "scripts/install"
[build.dependencies]
## 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]
[ext]
## 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
[ext.bootstrap]
check_installed_commands = [
"file /usr/bin/pkgr"
]
# any non-zero = fail
commands = [
"cp root/usr/bin/pkgr /usr/bin/pkgr"
]

View file

@ -0,0 +1,4 @@
[repo.main]
name = "Main"
url = "tcp://pkgs.ixvd.net:1050"

7
pkgr/skel/etc/pkgr.toml Normal file
View file

@ -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"

View file

@ -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

0
pkgr/src/api/client.rs Normal file
View file

View file

@ -1,27 +1,63 @@
use reqwest::blocking::Response;
use std::collections::HashMap;
use serde;
use serde::{Deserialize, Serialize};
pub struct APITransaction<R = Response> {
/// Query made to API.
query: String, //TODO: enum
/// Response struct from abstracted library
_response: Option<R>
}
pub mod client;
impl<R> APITransaction<R> {
pub fn new(_response: Option<R>) -> Self {
APITransaction {
query: String::default(),
_response
}
#[derive(Serialize, Deserialize)]
pub enum Query {
#[serde(rename = "pull")]
Pull {
name: String,
version: Option<u128>,
tags: Vec<String>
},
#[serde(rename = "push")]
Push {
// todo: review me pls
_data: Vec<u8>
},
#[serde(rename = "index")]
Index {
request_update: bool,
fetch: bool
}
}
pub struct API {
base_url: String,
#[derive(Serialize, Deserialize)]
pub struct Request {
version: u32,
id: String,
token: Option<String>,
history: Vec<APITransaction>
query: HashMap<String, Query>
}
impl API {
#[derive(Serialize, Deserialize)]
pub struct DataErrorDetails {
actor: String,
detailed_cause: String,
recovery_options: Vec<String>,
}
#[derive(Serialize, Deserialize)]
pub struct DataError {
name: String,
cause: Option<String>,
details: Option<DataErrorDetails>
}
#[derive(Serialize, Deserialize)]
pub enum Data {
Pull {
_data: Option<Vec<u8>>,
}
}
#[derive(Serialize, Deserialize)]
pub struct Response {
version: u32,
id: String,
reply_to: String,
errors: HashMap<String, DataError>,
data: HashMap<String, Data>
}

View file

@ -1,16 +1,17 @@
use std::process::exit;
use clap::{Parser, Subcommand};
use colored::Colorize;
use log::{debug, error, info, trace, warn};
use colored::Colorize;
use manifest::package::PackageType;
use crate::CONFIG;
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)]
@ -25,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
@ -57,6 +60,7 @@ impl Command {
match self {
Command::Install {
build,
ask,
package_identifier,
} => {
warn!("Installer does not run in isolation.");
@ -80,6 +84,15 @@ impl Command {
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();

View file

@ -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<String>,
#[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()
}
}
}

37
pkgr/src/config/repos.rs Normal file
View file

@ -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<String, Repo>
}
impl RepoFile {
pub fn from_path(path: &str) -> Result<RepoFile, String> {
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)),
}
}
}

View file

@ -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"),
}
}
}

View file

@ -9,6 +9,8 @@ mod commands;
mod logging;
/// Package and helpers.
mod package;
/// Repo and helpers
mod repo;
/// Process wrapper with logging wrapper.
mod process;
/// tmpfs wrapper.

View file

@ -1,22 +1,17 @@
use std::io;
use std::fs::remove_dir_all;
use std::path::Path;
use std::process::exit;
use std::{io, thread};
use libc::SIGINT;
use log::{debug, error, info, trace};
use log::{debug, trace};
use errors::{BinError, BuildError, InstallError};
use manifest::Manifest;
use pkgfile::PKGFile;
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, visit_dirs};
use crate::util::prompts::{is_noninteractive, prompt_bool};
use crate::util::fs::copy_recursively;
pub mod errors;
@ -114,11 +109,17 @@ impl PackageInstaller {
}
}
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(())
}
}

View file

@ -1,8 +1,11 @@
use std::fmt::{Display, Formatter};
use log::trace;
use crate::package::Package;
pub struct PackageQueue {
packages: Vec<Package>
packages: Vec<Package>,
}
impl PackageQueue {
@ -17,7 +20,7 @@ impl PackageQueue {
for dependency in dependencies {
trace!("Checking package: {}", &dependency.identifier);
if self.packages.contains(&dependency) {
continue
continue;
}
trace!("Adding package: {}", &dependency.identifier);
self.packages.push(dependency);
@ -31,7 +34,18 @@ impl PackageQueue {
self.packages
.iter_mut()
.for_each(|pkg| {
pkg.install(build);
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, "")
}
}

View file

@ -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<u32>,
tags: Vec<String>,
uri: Url
}

View file

@ -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<String, IndexPackage>
}
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<Repo> {
Ok(Repo::from_name(&self.origin_repo)?)
}
pub fn from_path(path: &str) -> Result<RepoIndex, String> {
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<Repo> for RepoIndex {
type Error = ();
/// Fetch
fn try_fetch(query: Repo) -> Result<Self, Self::Error> {
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(())
}
}
}

42
pkgr/src/repo/mod.rs Normal file
View file

@ -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<Repo> {
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) {
}
}

View file

@ -1,4 +1,4 @@
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use crate::CONFIG;

View file

@ -1,6 +1,7 @@
use std::path::Path;
use std::fs::DirEntry;
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<()> {

View file

@ -1,2 +1,9 @@
pub mod prompts;
/// Helpers for fs
pub mod fs;
/// Create a UUID
pub fn create_uuid() -> String {
// TODO
String::from("rand")
}

View file

@ -1,4 +1,5 @@
use std::io::Write;
use log::trace;
pub fn is_noninteractive() -> bool {