Files
flatpak-repo/src/upload.rs
T
2026-08-01 20:56:44 +02:00

92 lines
2.9 KiB
Rust

use anyhow::Context;
use axum::{body::Body, extract::State, http::StatusCode};
use futures::{StreamExt, future::ready, stream};
use tempfile::NamedTempFile;
use tokio::{io::AsyncWriteExt, process::Command, task::block_in_place};
use crate::{SharedState, util::e};
pub async fn flatpak_bundle(
State(state): State<SharedState>,
body: Body,
) -> Result<(), StatusCode> {
let mut stream = body.into_data_stream();
// Get first chunks
let Some(Ok(first_chunk)) = stream.next().await else {
tracing::warn!("Unexpected EOF");
return Err(StatusCode::BAD_REQUEST);
};
// Sanity check that it looks like a flatpak
// Technically not correct to require that the first chunk is 7 bytes or more, but who cares.
if !first_chunk.starts_with(b"flatpak") {
tracing::warn!("Not a valid flatpak bundle. Missing 'flatpak' magic string.");
return Err(StatusCode::BAD_REQUEST);
}
// Create a tmpfile
let (tmp_path, mut tmp_file) = block_in_place(|| {
let mut file = NamedTempFile::new()?;
file.disable_cleanup(true);
let path = file.path().to_path_buf();
let file = tokio::fs::File::from_std(file.into_file());
std::io::Result::Ok((path, file))
})
.context("Failed to create tmpfile")
.map_err(e(StatusCode::BAD_REQUEST))?;
// Write bundle to tmpfile
tracing::info!("Downloading flatpak to {tmp_path:?}");
let mut stream = stream::once(ready(Ok(first_chunk))).chain(stream);
loop {
let Some(chunk) = stream.next().await else {
break;
};
let chunk = chunk
.context("Stream error")
.map_err(e(StatusCode::BAD_REQUEST))?;
tmp_file
.write_all(&chunk)
.await
.context("Tmpfile write error")
.map_err(e(StatusCode::INTERNAL_SERVER_ERROR))?;
}
let repo = state.repo.lock().await;
// Import bundle into repository
tracing::info!("Importing {tmp_path:?} into repo {:?}", &*repo);
let output = Command::new("flatpak")
.arg("build-import-bundle")
.args([&*repo, &tmp_path])
.output()
.await
.context("Failed to execute flatpak build-import-bundle")
.map_err(e(StatusCode::INTERNAL_SERVER_ERROR))?;
if !output.status.success() {
tracing::error!("flatpak build-import-bundle failed");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
for line in stdout.lines() {
tracing::error!("[stdout] {line}");
}
for line in stderr.lines() {
tracing::error!("[stderr] {line}");
}
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
drop(tmp_file);
if let Err(e) = tokio::fs::remove_file(&tmp_path).await {
tracing::warn!("Failed to remove tmpfile at {tmp_path:?}: {e}");
}
tracing::info!("Success");
Ok(())
}