diff --git a/Cargo.lock b/Cargo.lock index 23d34fa..1549c85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1623,6 +1623,7 @@ dependencies = [ "env_logger 0.11.6", "maud", "rocket", + "serde_json", "sqlx", ] diff --git a/Cargo.toml b/Cargo.toml index d57aec6..2f55ecc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ based = { git = "https://git.hydrar.de/jmarya/based", features = ["htmx"]} env_logger = "0.11.6" maud = "0.26.0" rocket = "0.5.1" +serde_json = "1.0.134" sqlx = "0.8.2" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d211009 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM rust:buster as builder + +RUN rustup default nightly + +COPY . /app +WORKDIR /app + +RUN cargo build --release + +FROM archlinux + +RUN pacman -Syu --noconfirm + +COPY --from=builder /app/target/release/pacco /pacco + +WORKDIR / + +CMD ["/pacco"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1faf7f9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +services: + pacco: + build: . + ports: + - "8080:8000" + volumes: + - ./data:/data + environment: + - "RUST_LOG=info" + - "ROCKET_ADDRESS=0.0.0.0" diff --git a/src/main.rs b/src/main.rs index 66d34ad..739502a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,5 +32,9 @@ pub async fn index_page(ctx: RequestContext) -> StringResponse { #[rocket::launch] async fn launch() -> _ { env_logger::init(); - rocket::build().mount("/", routes![index_page, routes::pkg_route]) + rocket::build().mount("/", routes![ + index_page, + routes::pkg_route, + routes::upload_pkg + ]) } diff --git a/src/pkg.rs b/src/pkg.rs index b34c839..fecb16e 100644 --- a/src/pkg.rs +++ b/src/pkg.rs @@ -37,15 +37,15 @@ impl Repository { pub fn get_pkg(&self, arch: &str, pkg_name: &str) -> Option { let pkg = if pkg_name.ends_with(".pkg.tar.zst") { - Package::new(&self.name, arch, pkg_name.trim_end_matches(".pkg.tar.zst")) + Package::find(&self.name, arch, pkg_name.trim_end_matches(".pkg.tar.zst")) } else if pkg_name.ends_with(".pkg.tar.zst.sig") { - Package::new( + Package::find( &self.name, arch, pkg_name.trim_end_matches(".pkg.tar.zst.sig"), ) } else { - Package::new(&self.name, arch, pkg_name) + Package::find(&self.name, arch, pkg_name) }; if pkg.exists() { @@ -65,7 +65,16 @@ pub struct Package { } impl Package { - pub fn new(repo: &str, arch: &str, pkg_name: &str) -> Self { + pub fn new(repo: &str, arch: &str, pkg_name: &str, version: &str) -> Self { + Package { + repo: repo.to_string(), + arch: arch.to_string(), + name: pkg_name.to_string(), + version: Some(version.to_string()), + } + } + + pub fn find(repo: &str, arch: &str, pkg_name: &str) -> Self { Package { repo: repo.to_string(), arch: arch.to_string(), @@ -74,6 +83,27 @@ impl Package { } } + pub fn save(&self, pkg: Vec, sig: Option>) { + let pkg_file = self.base_path().join(&self.file_name()); + let sig_file = self.base_path().join(format!("{}.sig", self.file_name())); + + std::fs::write(&pkg_file, pkg).unwrap(); + if let Some(sig) = sig { + std::fs::write(sig_file, sig).unwrap(); + } + + let db_file = PathBuf::from("./data") + .join(&self.repo) + .join(&self.arch) + .join(format!("{}.db.tar.gz", self.repo)); + + run_command(vec![ + "repo-add", + db_file.to_str().unwrap(), + pkg_file.to_str().unwrap(), + ]); + } + fn base_path(&self) -> PathBuf { Path::new("./data").join(&self.repo).join(&self.arch) } @@ -152,3 +182,12 @@ impl Package { None } } + +pub fn run_command(cmd: Vec<&str>) { + std::process::Command::new(cmd.first().unwrap()) + .args(cmd.into_iter().skip(1).collect::>()) + .spawn() + .unwrap() + .wait() + .unwrap(); +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index c72ed99..78a2675 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,14 +1,60 @@ use based::page::{Shell, render_page}; +use based::request::api::FallibleApiResponse; use based::request::{RawResponse, RequestContext, StringResponse, respond_with}; use maud::html; -use rocket::get; use rocket::http::{ContentType, Status}; +use rocket::tokio::io::AsyncReadExt; +use rocket::{FromForm, get, post}; +use serde_json::json; -use crate::pkg::Repository; +use crate::pkg::{Package, Repository}; // /pkg/// // /pkg/// +use rocket::form::Form; +use rocket::fs::TempFile; + +#[derive(FromForm)] +pub struct PkgUpload<'r> { + name: String, + arch: String, + version: String, + pkg: TempFile<'r>, + sig: Option>, +} + +pub async fn tmp_file_to_vec<'r>(tmp: &TempFile<'r>) -> Vec { + let mut buf = Vec::with_capacity(tmp.len() as usize); + tmp.open() + .await + .unwrap() + .read_to_end(&mut buf) + .await + .unwrap(); + buf +} + +#[post("/pkg//upload", data = "")] +pub async fn upload_pkg( + repo: &str, + upload: Form>, + user: based::auth::User, +) -> FallibleApiResponse { + let pkg = Package::new(repo, &upload.arch, &upload.name, &upload.version); + + pkg.save( + tmp_file_to_vec(&upload.pkg).await, + if let Some(sig) = &upload.sig { + Some(tmp_file_to_vec(sig).await) + } else { + None + }, + ); + + Ok(json!({"ok": 1})) +} + #[get("/pkg///")] pub async fn pkg_route(repo: &str, arch: &str, pkg_name: &str, ctx: RequestContext) -> RawResponse { if let Some(repo) = Repository::new(repo) {