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