smart mirroring

This commit is contained in:
JMARyA 2025-01-13 11:39:00 +01:00
parent 68cb32f07b
commit 7647616242
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
13 changed files with 701 additions and 67 deletions

View file

@ -2,18 +2,25 @@ use based::auth::MaybeUser;
use based::page::{Shell, htmx_link, render_page};
use based::request::{RawResponse, RequestContext, StringResponse, respond_with};
use maud::{PreEscaped, html};
use rocket::get;
use pacco::pkg::mirror::MirrorRepository;
use rocket::http::{ContentType, Status};
use rocket::{State, get};
use pacco::pkg::Repository;
use pacco::pkg::arch::Architecture;
use crate::config::Config;
pub mod push;
pub mod ui;
pub mod user;
#[get("/")]
pub async fn index_page(ctx: RequestContext, user: MaybeUser) -> StringResponse {
pub async fn index_page(
ctx: RequestContext,
user: MaybeUser,
config: &State<Config>,
) -> StringResponse {
let repos: Vec<String> = Repository::list();
let content = html!(
@ -26,8 +33,12 @@ pub async fn index_page(ctx: RequestContext, user: MaybeUser) -> StringResponse
div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6" {
@for repo in repos {
(htmx_link(&format!("/{repo}"), "flex items-center gap-4 p-4 bg-gray-100 dark:bg-gray-700 rounded-lg shadow-md transition hover:bg-gray-200 dark:hover:bg-gray-600", "", html! {
p class="font-medium flex-1 text-gray-800 dark:text-gray-100" {
p class="font-medium text-gray-800 dark:text-gray-100" {
(repo)
@if config.is_mirrored_repo(&repo) {
div class="inline-block px-3 py-1 text-sm font-medium text-white bg-blue-500 rounded-full" { "Mirrored" };
};
};
}))
}
@ -38,15 +49,64 @@ pub async fn index_page(ctx: RequestContext, user: MaybeUser) -> StringResponse
}
#[get("/pkg/<repo>/<arch>/<pkg_name>")]
pub async fn pkg_route(repo: &str, arch: &str, pkg_name: &str) -> RawResponse {
pub async fn pkg_route(
repo: &str,
arch: &str,
pkg_name: &str,
config: &State<Config>,
) -> RawResponse {
let arch = Architecture::parse(arch).unwrap();
if config.is_mirrored_repo(repo) {
let repo = MirrorRepository::new(repo);
if is_repo_db(pkg_name) {
if pkg_name.ends_with("sig") {
return respond_with(
Status::Ok,
ContentType::new("application", "pgp-signature"),
repo.sig_content(arch, config.mirrorlist().unwrap())
.await
.unwrap(),
);
} else {
return respond_with(
Status::Ok,
ContentType::new("application", "tar"),
repo.db_content(arch, config.mirrorlist().unwrap())
.await
.unwrap(),
);
}
}
let pkg = repo
.get_pkg(pkg_name, config.mirrorlist().unwrap())
.await
.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(),
);
}
return respond_with(
Status::Ok,
ContentType::Plain,
"Not found".as_bytes().to_vec(),
);
}
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 is_repo_db(pkg_name) {
if pkg_name.ends_with("sig") {
return respond_with(
Status::Ok,
@ -107,3 +167,10 @@ pub async fn render(
)
.await
}
pub fn is_repo_db(pkg_name: &str) -> bool {
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")
}

View file

@ -1,6 +1,6 @@
use based::request::api::{FallibleApiResponse, api_error};
use rocket::tokio::io::AsyncReadExt;
use rocket::{FromForm, post};
use rocket::{FromForm, State, post};
use serde_json::json;
use pacco::pkg::Package;
@ -12,6 +12,8 @@ use pacco::pkg::arch::Architecture;
use rocket::form::Form;
use rocket::fs::TempFile;
use crate::config::Config;
#[derive(FromForm)]
pub struct PkgUpload<'r> {
name: String,
@ -37,12 +39,17 @@ pub async fn upload_pkg(
repo: &str,
upload: Form<PkgUpload<'_>>,
user: based::auth::APIUser,
config: &State<Config>,
) -> FallibleApiResponse {
// TODO : Permission System
if !user.0.is_admin() {
return Err(api_error("Forbidden"));
}
if config.is_mirrored_repo(repo) {
return Err(api_error("This repository is a mirror."));
}
let pkg = Package::new(
repo,
Architecture::parse(&upload.arch).ok_or_else(|| api_error("Invalid architecture"))?,

View file

@ -3,18 +3,30 @@ use based::{
request::{RequestContext, StringResponse},
};
use maud::{PreEscaped, html};
use rocket::get;
use rocket::{State, get};
use pacco::pkg::{Repository, arch::Architecture};
use pacco::pkg::{Repository, arch::Architecture, find_package_by_name};
use crate::config::Config;
use super::render;
// TODO : API
#[get("/<repo>/<pkg_name>")]
pub async fn pkg_ui(repo: &str, pkg_name: &str, ctx: RequestContext) -> Option<StringResponse> {
#[get("/<repo>/<pkg_name>?<ver>")]
pub async fn pkg_ui(
repo: &str,
pkg_name: &str,
ctx: RequestContext,
ver: Option<&str>,
) -> Option<StringResponse> {
let repo = Repository::new(repo).unwrap();
let pkg = repo.get_pkg_by_name(pkg_name)?;
let mut pkg = repo.get_pkg_by_name(pkg_name)?;
if let Some(ver) = ver {
pkg = pkg.get_version(ver);
}
let versions = pkg.versions();
let arch = pkg.arch();
let install_script = pkg.install_script();
@ -88,16 +100,17 @@ pub async fn pkg_ui(repo: &str, pkg_name: &str, ctx: RequestContext) -> Option<S
h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300" { "Versions" }
ul class="space-y-1" {
@for version in versions {
// TODO : Implement page per version ?version=
li class="text-gray-800 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400 transition" {
(version)
(htmx_link(&format!("/{}/{}?ver={version}", repo.name, &pkg.name), if pkg.version.as_ref().map(|x| *x == version).unwrap_or_default() { "text-blue-500" } else { "" }, "", html! {
(version)
}))
};
}
}
}
};
div class="flex flex-wrap pt-6" {
div class="flex flex-wrap pt-6" {
div class="space-y-2" {
h2 class="text-xl font-bold text-gray-700 dark:text-gray-300" { "Content" }
@ -166,8 +179,12 @@ pub async fn pkg_ui(repo: &str, pkg_name: &str, ctx: RequestContext) -> Option<S
}
#[get("/<repo>?<arch>")]
pub async fn repo_ui(repo: &str, ctx: RequestContext, arch: Option<&str>) -> StringResponse {
// TODO : permissions
pub async fn repo_ui(
repo: &str,
ctx: RequestContext,
arch: Option<&str>,
config: &State<Config>,
) -> StringResponse {
let arch = arch.map(|x| Architecture::parse(x).unwrap_or(Architecture::any));
let repo = Repository::new(repo).unwrap();
@ -181,13 +198,16 @@ pub async fn repo_ui(repo: &str, ctx: RequestContext, arch: Option<&str>) -> Str
let content = html! {
// Repository name and architectures
div class="flex flex-wrap items-center justify-center mb-6" {
h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100 pr-4" {
h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100" {
(repo.name)
};
div class="flex gap-2 mt-2 md:mt-0" {
@if config.is_mirrored_repo(&repo.name) {
div class="inline-block px-3 py-1 text-sm font-medium text-white bg-blue-500 rounded-full ml-4" { "Mirrored" };
};
div class="flex gap-2 mt-2 md:mt-0 ml-4" {
@for a in architectures {
// TODO : Filter per arch with ?arch=
@if let Some(arch) = arch.as_ref() {
@if arch.to_string() == a {
(htmx_link(&format!("/{}", repo.name), "px-3 py-1 text-sm font-medium bg-blue-400 dark:bg-blue-500 text-gray-600 dark:text-gray-300 rounded-full", "", html! {
@ -258,12 +278,10 @@ pub fn build_info(key: String, value: String) -> PreEscaped<String> {
);
}
"depend" => {
// TODO : Find package link
return key_value("Depends on".to_string(), value);
return pkg_list_info("Depends on", &value);
}
"makedepend" => {
// TODO : Find package link
return key_value("Build Dependencies".to_string(), value);
return pkg_list_info("Build Dependencies", &value);
}
"license" => {
return key_value("License".to_string(), value);
@ -307,3 +325,36 @@ pub fn take_out<T>(v: &mut Vec<T>, f: impl Fn(&T) -> bool) -> (&mut Vec<T>, Opti
(v, None)
}
pub fn find_pkg_url(pkg: &str) -> Option<String> {
if let Some(pkg) = find_package_by_name(pkg) {
return Some(format!("/{}/{}", pkg.repo, pkg.name));
}
None
}
pub fn pkg_list_info(key: &str, value: &str) -> PreEscaped<String> {
let pkgs = value.split_whitespace().map(|pkg| {
if let Some(pkg_url) = find_pkg_url(pkg) {
html! {
a href=(pkg_url) class="ml-2 text-blue-400" { (pkg) };
}
} else {
html! {
span class="ml-2" { (pkg) };
}
}
});
html! {
div class="flex items-center" {
span class="font-bold w-32" { (format!("{key}: ")) };
div class="flex flex-wrap" {
@for pkg in pkgs {
(pkg)
};
};
};
}
}