init
This commit is contained in:
commit
221b2a82e7
7 changed files with 3817 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
3534
Cargo.lock
generated
Normal file
3534
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "pacco"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
based = { git = "https://git.hydrar.de/jmarya/based", features = ["htmx"]}
|
||||||
|
env_logger = "0.11.6"
|
||||||
|
maud = "0.26.0"
|
||||||
|
rocket = "0.5.1"
|
||||||
|
sqlx = "0.8.2"
|
9
README.md
Normal file
9
README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Pacco
|
||||||
|
Pacco is an application for managing and hosting pacman repositories.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Multiple repositories
|
||||||
|
- Multiple architectures
|
||||||
|
- Web UI for packages
|
||||||
|
- API for pushing new packages
|
||||||
|
- Smart mirroring
|
36
src/main.rs
Normal file
36
src/main.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// TODO :
|
||||||
|
// - Base
|
||||||
|
// - API
|
||||||
|
// - UI
|
||||||
|
// - PkgDB Abstraction
|
||||||
|
// - Pkg Abstraction
|
||||||
|
|
||||||
|
use based::page::{Shell, render_page};
|
||||||
|
use based::request::{RequestContext, StringResponse};
|
||||||
|
use maud::html;
|
||||||
|
use rocket::get;
|
||||||
|
use rocket::routes;
|
||||||
|
|
||||||
|
pub mod pkg;
|
||||||
|
pub mod routes;
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
pub async fn index_page(ctx: RequestContext) -> StringResponse {
|
||||||
|
let content = html!(
|
||||||
|
h1 { "Hello World!" };
|
||||||
|
);
|
||||||
|
|
||||||
|
render_page(
|
||||||
|
content,
|
||||||
|
"Hello World",
|
||||||
|
ctx,
|
||||||
|
&Shell::new(html! {}, html! {}, Some(String::new())),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::launch]
|
||||||
|
async fn launch() -> _ {
|
||||||
|
env_logger::init();
|
||||||
|
rocket::build().mount("/", routes![index_page, routes::pkg_route])
|
||||||
|
}
|
154
src/pkg.rs
Normal file
154
src/pkg.rs
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
pub struct Repository {
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Repository {
|
||||||
|
pub fn new(name: &str) -> Option<Self> {
|
||||||
|
if PathBuf::from("./data").join(name).exists() {
|
||||||
|
Some(Repository {
|
||||||
|
name: name.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base_path(&self, arch: &str) -> PathBuf {
|
||||||
|
PathBuf::from("./data").join(&self.name).join(arch)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn db_content(&self, arch: &str) -> Option<Vec<u8>> {
|
||||||
|
std::fs::read(
|
||||||
|
self.base_path(arch)
|
||||||
|
.join(format!("{}.db.tar.gz", self.name)),
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sig_content(&self, arch: &str) -> Option<Vec<u8>> {
|
||||||
|
std::fs::read(
|
||||||
|
self.base_path(arch)
|
||||||
|
.join(format!("{}.db.tar.gz.sig", self.name)),
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pkg(&self, arch: &str, pkg_name: &str) -> Option<Package> {
|
||||||
|
let pkg = if pkg_name.ends_with(".pkg.tar.zst") {
|
||||||
|
Package::new(&self.name, arch, pkg_name.trim_end_matches(".pkg.tar.zst"))
|
||||||
|
} else if pkg_name.ends_with(".pkg.tar.zst.sig") {
|
||||||
|
Package::new(
|
||||||
|
&self.name,
|
||||||
|
arch,
|
||||||
|
pkg_name.trim_end_matches(".pkg.tar.zst.sig"),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Package::new(&self.name, arch, pkg_name)
|
||||||
|
};
|
||||||
|
|
||||||
|
if pkg.exists() {
|
||||||
|
return Some(pkg);
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Package {
|
||||||
|
repo: String,
|
||||||
|
arch: String,
|
||||||
|
name: String,
|
||||||
|
version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Package {
|
||||||
|
pub fn new(repo: &str, arch: &str, pkg_name: &str) -> Self {
|
||||||
|
Package {
|
||||||
|
repo: repo.to_string(),
|
||||||
|
arch: arch.to_string(),
|
||||||
|
name: pkg_name.to_string(),
|
||||||
|
version: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_path(&self) -> PathBuf {
|
||||||
|
Path::new("./data").join(&self.repo).join(&self.arch)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn switch_arch(&self, arch: &str) -> Self {
|
||||||
|
let mut new = self.clone();
|
||||||
|
new.arch = arch.to_string();
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exists(&self) -> bool {
|
||||||
|
let pkg_file = self.base_path().join(self.file_name());
|
||||||
|
pkg_file.exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_version(&self, version: &str) -> Self {
|
||||||
|
let mut new_pkg = self.clone();
|
||||||
|
new_pkg.version = Some(version.to_string());
|
||||||
|
new_pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_signed(&self) -> bool {
|
||||||
|
let signed_file = self.base_path().join(format!("{}.sig", self.file_name()));
|
||||||
|
signed_file.exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file_name(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"{}-{}-{}-{}.pkg.tar.zst",
|
||||||
|
self.name,
|
||||||
|
if let Some(ver) = &self.version {
|
||||||
|
ver.to_string()
|
||||||
|
} else {
|
||||||
|
let versions = self.versions();
|
||||||
|
versions.first().unwrap().clone()
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
self.arch,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn versions(&self) -> Vec<String> {
|
||||||
|
let dir_path = self.base_path();
|
||||||
|
let mut versions = vec![];
|
||||||
|
|
||||||
|
if let Ok(entries) = std::fs::read_dir(dir_path) {
|
||||||
|
for entry in entries.filter_map(Result::ok) {
|
||||||
|
let file_name = entry.file_name().into_string().unwrap_or_default();
|
||||||
|
if file_name.starts_with(&self.name) && file_name.ends_with(".pkg.tar.zst") {
|
||||||
|
// Extract version (assuming the filename is "<pkg_name>-<version>-<relation>-<arch>.pkg.tar.zst")
|
||||||
|
if let Some(version) = file_name.split('-').nth(1) {
|
||||||
|
versions.push(version.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort versions in descending order (most recent version first)
|
||||||
|
versions.sort_by(|a, b| b.cmp(a));
|
||||||
|
versions
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pkg_content(&self) -> Option<Vec<u8>> {
|
||||||
|
if self.exists() {
|
||||||
|
return std::fs::read(self.base_path().join(self.file_name())).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sig_content(&self) -> Option<Vec<u8>> {
|
||||||
|
if self.exists() {
|
||||||
|
return std::fs::read(self.base_path().join(format!("{}.sig", &self.file_name()))).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
72
src/routes/mod.rs
Normal file
72
src/routes/mod.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use based::page::{Shell, render_page};
|
||||||
|
use based::request::{RawResponse, RequestContext, StringResponse, respond_with};
|
||||||
|
use maud::html;
|
||||||
|
use rocket::get;
|
||||||
|
use rocket::http::{ContentType, Status};
|
||||||
|
|
||||||
|
use crate::pkg::Repository;
|
||||||
|
|
||||||
|
// /pkg/<repo>/<arch>/<pkg_name>
|
||||||
|
// /pkg/<repo>/<arch>/
|
||||||
|
|
||||||
|
#[get("/pkg/<repo>/<arch>/<pkg_name>")]
|
||||||
|
pub async fn pkg_route(repo: &str, arch: &str, pkg_name: &str, ctx: RequestContext) -> RawResponse {
|
||||||
|
if let Some(repo) = Repository::new(repo) {
|
||||||
|
if pkg_name.ends_with("db.tar.gz")
|
||||||
|
|| pkg_name.ends_with("db")
|
||||||
|
|| pkg_name.ends_with("db.tar.gz.sig")
|
||||||
|
|| pkg_name.ends_with("db.sig")
|
||||||
|
{
|
||||||
|
if pkg_name.ends_with("sig") {
|
||||||
|
return respond_with(
|
||||||
|
Status::Ok,
|
||||||
|
ContentType::new("application", "pgp-signature"),
|
||||||
|
repo.sig_content(arch).unwrap(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return respond_with(
|
||||||
|
Status::Ok,
|
||||||
|
ContentType::new("application", "tar"),
|
||||||
|
repo.db_content(arch).unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pkg = repo.get_pkg(arch, pkg_name).unwrap();
|
||||||
|
|
||||||
|
if pkg_name.ends_with("pkg.tar.zst") {
|
||||||
|
return respond_with(
|
||||||
|
Status::Ok,
|
||||||
|
ContentType::new("application", "tar"),
|
||||||
|
pkg.pkg_content().unwrap(),
|
||||||
|
);
|
||||||
|
} else if pkg_name.ends_with("pkg.tar.zst.sig") {
|
||||||
|
return respond_with(
|
||||||
|
Status::Ok,
|
||||||
|
ContentType::new("application", "pgp-signature"),
|
||||||
|
pkg.sig_content().unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
respond_with(
|
||||||
|
Status::Ok,
|
||||||
|
ContentType::Plain,
|
||||||
|
"Not found".as_bytes().to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
pub async fn index_page(ctx: RequestContext) -> StringResponse {
|
||||||
|
let content = html!(
|
||||||
|
h1 { "Hello World!" };
|
||||||
|
);
|
||||||
|
|
||||||
|
render_page(
|
||||||
|
content,
|
||||||
|
"Hello World",
|
||||||
|
ctx,
|
||||||
|
&Shell::new(html! {}, html! {}, Some(String::new())),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue