diff --git a/Cargo.lock b/Cargo.lock index 62dfedf..a57f939 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,6 +153,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.4.0" @@ -209,8 +215,8 @@ dependencies = [ "rand", "rayon", "regex", - "reqwest", - "ring", + "reqwest 0.11.27", + "ring 0.16.20", "rocket", "rocket_cors", "serde", @@ -900,6 +906,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.2.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1010,6 +1035,29 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.2.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.9.5" @@ -1038,9 +1086,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", - "http-body", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -1052,6 +1100,43 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.7", + "http 1.2.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.2.0", + "hyper 1.5.2", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1059,12 +1144,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.32", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.5.2", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "hyper 1.5.2", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -1643,10 +1763,13 @@ dependencies = [ "env_logger 0.11.6", "log", "maud", + "reqwest 0.12.12", "rocket", + "serde", "serde_json", "sqlx", "tar", + "toml", "zstd", ] @@ -1959,11 +2082,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", - "http-body", - "hyper", - "hyper-tls", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -1972,12 +2095,12 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", "tokio", "tokio-native-tls", "tower-service", @@ -1988,6 +2111,50 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.7", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.2", + "hyper-rustls", + "hyper-tls 0.6.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "system-configuration 0.6.1", + "tokio", + "tokio-native-tls", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + [[package]] name = "ring" version = "0.16.20" @@ -1998,11 +2165,26 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + [[package]] name = "rocket" version = "0.5.1" @@ -2085,7 +2267,7 @@ dependencies = [ "either", "futures", "http 0.2.12", - "hyper", + "hyper 0.14.32", "indexmap", "log", "memchr", @@ -2141,6 +2323,19 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.23.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2150,6 +2345,32 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.19" @@ -2631,6 +2852,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -2650,7 +2880,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.7.0", + "core-foundation", + "system-configuration-sys 0.6.0", ] [[package]] @@ -2663,6 +2904,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tar" version = "0.4.43" @@ -2822,6 +3073,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -2880,6 +3141,27 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -3034,6 +3316,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -3258,6 +3546,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index a7173ac..895a6a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,11 @@ chrono = "0.4.39" env_logger = "0.11.6" log = "0.4.22" maud = "0.26.0" +reqwest = { version = "0.12.12" } rocket = "0.5.1" +serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.134" sqlx = "0.8.2" tar = "0.4.43" +toml = "0.8.19" zstd = "0.13.2" diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..22b0cad --- /dev/null +++ b/config.toml @@ -0,0 +1,15 @@ + +[mirror] +repos = [ + "core", + "extra", + "multilib" +] + +mirrorlist = [ + "http://mirrors.kernel.org/archlinux/$repo/os/$arch", + "https://geo.mirror.pkgbuild.com/$repo/os/$arch", + "https://ftpmirror.infania.net/mirror/archlinux/$repo/os/$arch", + "http://mirror.rackspace.com/archlinux/$repo/os/$arch", + "https://mirror.rackspace.com/archlinux/$repo/os/$arch" +] diff --git a/docker-compose.yml b/docker-compose.yml index a84d603..bc5d514 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,7 @@ services: - "8080:8000" volumes: - ./data:/data + - ./config.toml:/config.toml environment: - "RUST_LOG=info" - "ROCKET_ADDRESS=0.0.0.0" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..1388bee --- /dev/null +++ b/src/config.rs @@ -0,0 +1,30 @@ +use serde::Deserialize; + +#[derive(Debug, Clone, Deserialize, Default)] +pub struct Config { + pub mirror: Option, +} + +#[derive(Debug, Clone, Deserialize, Default)] +pub struct MirrorConfig { + repos: Vec, + mirrorlist: Vec, +} + +impl Config { + pub fn is_mirrored_repo(&self, repo: &str) -> bool { + if let Some(mirrorc) = &self.mirror { + return mirrorc.repos.iter().any(|x| x == repo); + } + + false + } + + pub fn mirrorlist(&self) -> Option<&[String]> { + if let Some(mirror) = &self.mirror { + return Some(mirror.mirrorlist.as_slice()); + } + + None + } +} diff --git a/src/main.rs b/src/main.rs index 7d1ee5c..8bbff53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ use based::auth::User; use based::get_pg; +use config::Config; use rocket::routes; +pub mod config; pub mod routes; #[rocket::launch] @@ -11,21 +13,27 @@ async fn launch() -> _ { let pg = get_pg!(); sqlx::migrate!("./migrations").run(pg).await.unwrap(); + let config: Config = + toml::from_str(&std::fs::read_to_string("config.toml").unwrap_or_default()) + .unwrap_or_default(); + let _ = User::create("admin".to_string(), "admin", based::auth::UserRole::Admin).await; - rocket::build().mount("/", routes![ - based::htmx::htmx_script_route, - routes::index_page, - routes::pkg_route, - routes::push::upload_pkg, - routes::user::login, - routes::user::login_post, - routes::user::account_page, - routes::ui::pkg_ui, - routes::ui::repo_ui, - routes::user::new_api_key, - routes::user::end_session, - routes::user::change_password, - routes::user::change_password_post - ]) + rocket::build() + .mount("/", routes![ + based::htmx::htmx_script_route, + routes::index_page, + routes::pkg_route, + routes::push::upload_pkg, + routes::user::login, + routes::user::login_post, + routes::user::account_page, + routes::ui::pkg_ui, + routes::ui::repo_ui, + routes::user::new_api_key, + routes::user::end_session, + routes::user::change_password, + routes::user::change_password_post + ]) + .manage(config) } diff --git a/src/pkg/mirror.rs b/src/pkg/mirror.rs new file mode 100644 index 0000000..d8efbfb --- /dev/null +++ b/src/pkg/mirror.rs @@ -0,0 +1,120 @@ +use std::path::PathBuf; + +use super::{Package, Repository, arch::Architecture}; + +pub struct MirrorRepository { + pub inner: Repository, +} + +impl MirrorRepository { + pub fn new(repo: &str) -> Self { + std::fs::create_dir_all(format!("./data/{repo}")).unwrap(); + Self { + inner: Repository::new(repo).unwrap(), + } + } + + pub async fn download_file( + &self, + url: &str, + file_path: PathBuf, + mirrorlist: &[String], + arch: Architecture, + ) { + log::info!("Downloading {url} to {}", file_path.to_str().unwrap()); + for mirror in mirrorlist { + let mirror = mirror + .replace("$repo", &self.inner.name) + .replace("$arch", &arch.to_string()); + let url = format!("{mirror}/{url}"); + log::info!("Trying mirror {url}"); + + let client = reqwest::Client::new(); + if let Ok(resp) = client.get(url).send().await { + if resp.status().is_success() { + let data = resp.bytes().await.unwrap().to_vec(); + let parent = file_path.parent().unwrap(); + std::fs::create_dir_all(parent).unwrap(); + + std::fs::write(file_path, data).unwrap(); + return; + } else { + log::warn!("Mirror {mirror} failed [{}]", resp.status().as_u16()); + } + } + } + } + + /// Get the `.db.tar.gz` content for the repository of `arch` + pub async fn db_content(&self, arch: Architecture, mirrorlist: &[String]) -> Option> { + if let Some(content) = self.inner.db_content(arch.clone()) { + return Some(content); + } + + self.download_file( + &format!("{}.db.tar.gz", self.inner.name), + self.inner + .base_path(arch.clone()) + .join(format!("{}.db.tar.gz", self.inner.name)), + mirrorlist, + arch.clone(), + ) + .await; + + self.inner.db_content(arch) + } + + /// Get the `.db.tar.gz.sig` content for the repository of `arch` + pub async fn sig_content(&self, arch: Architecture, mirrorlist: &[String]) -> Option> { + if let Some(content) = self.inner.sig_content(arch.clone()) { + return Some(content); + } + + self.download_file( + &format!("{}.db.tar.gz.sig", self.inner.name), + self.inner + .base_path(arch.clone()) + .join(format!("{}.db.tar.gz.sig", self.inner.name)), + mirrorlist, + arch.clone(), + ) + .await; + + self.inner.sig_content(arch) + } + + pub async fn get_pkg(&self, pkg_name: &str, mirrorlist: &[String]) -> Option { + if let Some(pkg) = self.inner.get_pkg(pkg_name) { + return Some(pkg); + } + + // PKG + let (name, _, _, arch) = Package::extract_pkg_name(pkg_name).unwrap(); + + self.download_file( + pkg_name, + self.inner + .base_path(arch.clone()) + .join(&name) + .join(pkg_name), + mirrorlist, + arch.clone(), + ) + .await; + + // SIG + + self.download_file( + &format!("{pkg_name}.sig"), + self.inner + .base_path(arch.clone()) + .join(&name) + .join(format!("{pkg_name}.sig")), + mirrorlist, + arch.clone(), + ) + .await; + + self.inner.get_pkg(pkg_name) + } +} diff --git a/src/pkg/mod.rs b/src/pkg/mod.rs index 6d7a305..b4d03fb 100644 --- a/src/pkg/mod.rs +++ b/src/pkg/mod.rs @@ -1,8 +1,20 @@ -// TODO : Read DB Info - pub mod repo; pub use repo::Repository; pub mod package; pub use package::Package; pub mod arch; pub mod db; +pub mod mirror; + +pub fn find_package_by_name(pkg: &str) -> Option { + for repo in Repository::list() { + let repo = Repository::new(&repo).unwrap(); + let pkgs = repo.list_pkg(); + + if let Some(res) = pkgs.iter().find(|x| *x == pkg) { + return repo.get_pkg_by_name(res); + } + } + + None +} diff --git a/src/pkg/package.rs b/src/pkg/package.rs index e626dd7..d960294 100644 --- a/src/pkg/package.rs +++ b/src/pkg/package.rs @@ -18,18 +18,21 @@ pub struct Package { /// Name of the package pub name: String, /// Version of the package - version: Option, + pub version: Option, } impl Package { /// Create a new package pub fn new(repo: &str, arch: Architecture, pkg_name: &str, version: &str) -> Self { - Package { + let pkg = Package { repo: repo.to_string(), arch, name: pkg_name.to_string(), version: Some(version.to_string()), - } + }; + + std::fs::create_dir_all(pkg.base_path()).unwrap(); + pkg } pub fn install_script(&self) -> Option { @@ -186,7 +189,7 @@ impl Package { pub fn pacman_hooks(&self) -> Vec<(String, String)> { let pkg_file = self.base_path().join(self.file_name()); - let files = list_tar_file(&pkg_file).unwrap_or_default(); + let files = self.file_list(); files .into_iter() .filter(|x| { @@ -237,12 +240,10 @@ impl Package { fn base_path(&self) -> PathBuf { // /// - let p = Path::new("./data") + Path::new("./data") .join(&self.repo) .join(self.arch.to_string()) - .join(&self.name); - std::fs::create_dir_all(&p).unwrap(); - p + .join(&self.name) } /// Switch the `Architecture` of the package diff --git a/src/pkg/repo.rs b/src/pkg/repo.rs index 3f854b0..63fdae5 100644 --- a/src/pkg/repo.rs +++ b/src/pkg/repo.rs @@ -18,6 +18,7 @@ impl Repository { repos.push(file_name); } + repos.sort(); repos } diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 3a454a9..6d4eea2 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -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, +) -> StringResponse { let repos: Vec = 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///")] -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, +) -> 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") +} diff --git a/src/routes/push.rs b/src/routes/push.rs index 5d51c57..4477d52 100644 --- a/src/routes/push.rs +++ b/src/routes/push.rs @@ -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>, user: based::auth::APIUser, + config: &State, ) -> 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"))?, diff --git a/src/routes/ui.rs b/src/routes/ui.rs index ca59d5d..8b91b2d 100644 --- a/src/routes/ui.rs +++ b/src/routes/ui.rs @@ -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("//")] -pub async fn pkg_ui(repo: &str, pkg_name: &str, ctx: RequestContext) -> Option { +#[get("//?")] +pub async fn pkg_ui( + repo: &str, + pkg_name: &str, + ctx: RequestContext, + ver: Option<&str>, +) -> Option { 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 Option?")] -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, +) -> 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 { ); } "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(v: &mut Vec, f: impl Fn(&T) -> bool) -> (&mut Vec, Opti (v, None) } + +pub fn find_pkg_url(pkg: &str) -> Option { + 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 { + 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) + }; + }; + }; + } +}