Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
5ce0ece691
|
36
Cargo.lock
generated
36
Cargo.lock
generated
@ -1299,6 +1299,19 @@ dependencies = [
|
||||
"hashbrown 0.15.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
|
||||
dependencies = [
|
||||
"console",
|
||||
"number_prefix",
|
||||
"portable-atomic",
|
||||
"unicode-width",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
@ -1473,6 +1486,7 @@ dependencies = [
|
||||
"config",
|
||||
"dialoguer",
|
||||
"env_logger",
|
||||
"indicatif",
|
||||
"log",
|
||||
"nostr-sdk",
|
||||
"reqwest",
|
||||
@ -1642,6 +1656,12 @@ dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "number_prefix"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
@ -1877,6 +1897,12 @@ dependencies = [
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
@ -3055,6 +3081,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.8"
|
||||
|
@ -21,6 +21,7 @@ tokio = { version = "1.43.0", features = ["fs", "rt", "macros", "rt-multi-thread
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
async-trait = "0.1.86"
|
||||
semver = "1.0.25"
|
||||
indicatif = "0.17.11"
|
||||
dialoguer = "0.11.0"
|
||||
env_logger = "0.11.6"
|
||||
sha2 = "0.10.8"
|
||||
|
@ -1,10 +1,36 @@
|
||||
use anyhow::{bail, Result};
|
||||
use apk::res::Chunk;
|
||||
use apk::AndroidManifest;
|
||||
use log::debug;
|
||||
use log::{debug, trace};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::io::Cursor;
|
||||
|
||||
/// Converts [Chunk::Xml] to actual XML string
|
||||
pub fn xml_chunk_to_xml(chunk: &Chunk) -> Result<String> {
|
||||
let nodes = if let Chunk::Xml(nodes) = chunk {
|
||||
nodes
|
||||
} else {
|
||||
bail!("Not an XML chunk")
|
||||
};
|
||||
|
||||
let mut buf = String::with_capacity(4096);
|
||||
for node in nodes {
|
||||
match node {
|
||||
Chunk::Xml(x) => buf.write_str(&xml_chunk_to_xml(&Chunk::Xml(x.clone()))?)?,
|
||||
Chunk::XmlStartNamespace(_, _) => {}
|
||||
Chunk::XmlEndNamespace(_, _) => {}
|
||||
Chunk::XmlStartElement(_, _, _) => {}
|
||||
Chunk::XmlEndElement(_, _) => {}
|
||||
Chunk::XmlResourceMap(_) => {}
|
||||
Chunk::TablePackage(_, _) => {}
|
||||
Chunk::TableType(_, _, _) => {}
|
||||
Chunk::TableTypeSpec(_, _) => {}
|
||||
_ => trace!("Skipping chunk: {:?}", node),
|
||||
}
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
/// Parse android manifest from AndroidManifest.xml file data
|
||||
pub fn parse_android_manifest(data: &[u8]) -> Result<AndroidManifest> {
|
||||
let chunks = if let Chunk::Xml(chunks) = Chunk::parse(&mut Cursor::new(data))? {
|
||||
|
@ -30,6 +30,7 @@ impl ApkSigningBlock {
|
||||
pub fn get_signatures(&self) -> Result<Vec<ApkSignatureBlock>> {
|
||||
const V2_SIG_BLOCK_ID: u32 = 0x7109871a;
|
||||
const V3_SIG_BLOCK_ID: u32 = 0xf05368c0;
|
||||
const NOSTR_SIG_BLOCK_ID: u32 = 0x4e535452; // "NSTR"
|
||||
|
||||
let mut sigs = vec![];
|
||||
for (k, v) in &self.data {
|
||||
@ -158,6 +159,12 @@ pub enum ApkSignatureBlock {
|
||||
min_sdk: u32,
|
||||
max_sdk: u32,
|
||||
},
|
||||
|
||||
/// Nostr Schnorr sig
|
||||
Nostr {
|
||||
signature: Vec<u8>,
|
||||
public_key: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Display for ApkSignatureBlock {
|
||||
@ -190,6 +197,15 @@ impl Display for ApkSignatureBlock {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ApkSignatureBlock::Nostr {
|
||||
public_key,
|
||||
signature,
|
||||
} => write!(
|
||||
f,
|
||||
"Nostr: pubkey={}, sig={}",
|
||||
hex::encode(&public_key),
|
||||
hex::encode(&signature)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -323,7 +339,7 @@ fn get_lv_u32(slice: &[u8]) -> Result<&[u8]> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn take_lv_u32<'a>(slice: &mut &'a[u8]) -> Result<&'a [u8]> {
|
||||
fn take_lv_u32<'a>(slice: &mut &'a [u8]) -> Result<&'a [u8]> {
|
||||
let len = u32::from_le_bytes(slice[..4].try_into()?);
|
||||
ensure!(
|
||||
len <= (slice.len() - 4) as u32,
|
||||
|
36
src/main.rs
36
src/main.rs
@ -9,11 +9,10 @@ use config::{Config, File};
|
||||
use log::info;
|
||||
use nostr_sdk::prelude::Coordinate;
|
||||
use nostr_sdk::{Client, EventBuilder, Keys, Kind, Tag};
|
||||
use semver::Version;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
#[command(about)]
|
||||
#[command(version, about)]
|
||||
struct Args {
|
||||
/// User specified config path
|
||||
#[arg(long, short)]
|
||||
@ -22,10 +21,6 @@ struct Args {
|
||||
/// Relay to publish events to
|
||||
#[arg(long)]
|
||||
pub relay: Vec<String>,
|
||||
|
||||
/// Use specific version
|
||||
#[arg(long)]
|
||||
pub version: Option<Version>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@ -46,19 +41,16 @@ async fn main() -> Result<()> {
|
||||
|
||||
let repo: Box<dyn Repo> = (&manifest).try_into()?;
|
||||
|
||||
let mut releases = repo.get_releases().await?;
|
||||
let releases = repo.get_releases().await?;
|
||||
|
||||
info!("Found {} release(s)", releases.len());
|
||||
|
||||
releases.sort_by(|a, b| b.version.partial_cmp(&a.version).unwrap());
|
||||
if let Some(release) = releases.iter_mut().find(|r| {
|
||||
args.version
|
||||
.as_ref()
|
||||
.map(|v| v == &r.version)
|
||||
.unwrap_or(true)
|
||||
}) {
|
||||
if let Some(release) = releases.first() {
|
||||
info!("Starting publish of release {}", release.version);
|
||||
|
||||
info!("Artifacts: ");
|
||||
for a in &release.artifacts {
|
||||
info!(" - {}", a);
|
||||
}
|
||||
if !dialoguer::Confirm::new()
|
||||
.default(false)
|
||||
.with_prompt(format!("Publish v{}?", release.version))
|
||||
@ -67,20 +59,6 @@ async fn main() -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
release.load_artifacts().await?;
|
||||
info!("Artifacts: ");
|
||||
for a in &release.artifacts {
|
||||
info!(" - {}", a);
|
||||
}
|
||||
|
||||
if !dialoguer::Confirm::new()
|
||||
.default(false)
|
||||
.with_prompt("Continue?")
|
||||
.interact()?
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let key = dialoguer::Password::new()
|
||||
.with_prompt("Enter nsec:")
|
||||
.interact()?;
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::repo::{ArtifactMetadata, Platform, Repo, RepoArtifact, RepoRelease, RepoResource};
|
||||
use crate::repo::{load_artifact_url, Repo, RepoRelease};
|
||||
use anyhow::{anyhow, Result};
|
||||
use log::{info, warn};
|
||||
use nostr_sdk::prelude::hex;
|
||||
use nostr_sdk::Url;
|
||||
use reqwest::header::{HeaderMap, ACCEPT, USER_AGENT};
|
||||
use reqwest::Client;
|
||||
@ -61,19 +60,6 @@ struct GithubReleaseArtifact {
|
||||
pub size: u64,
|
||||
pub content_type: String,
|
||||
pub browser_download_url: String,
|
||||
pub digest: Option<String>,
|
||||
}
|
||||
|
||||
fn decode_digest(digest: &Option<String>) -> Result<Vec<u8>> {
|
||||
if let Some(s) = digest {
|
||||
if s.starts_with("sha256:") {
|
||||
Ok(hex::decode(&s[7..])?)
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@ -97,40 +83,31 @@ impl Repo for GithubRepo {
|
||||
for release in gh_release {
|
||||
let mut artifacts = vec![];
|
||||
for gh_artifact in release.assets {
|
||||
artifacts.push(RepoArtifact {
|
||||
name: gh_artifact.name,
|
||||
size: gh_artifact.size,
|
||||
location: RepoResource::Remote(gh_artifact.browser_download_url),
|
||||
content_type: gh_artifact.content_type,
|
||||
platform: Platform::Unknown,
|
||||
metadata: ArtifactMetadata::Empty,
|
||||
hash: decode_digest(&gh_artifact.digest)?,
|
||||
})
|
||||
match load_artifact_url(&gh_artifact.browser_download_url).await {
|
||||
Ok(a) => artifacts.push(a),
|
||||
Err(e) => warn!(
|
||||
"Failed to load artifact {}: {}",
|
||||
gh_artifact.browser_download_url, e
|
||||
),
|
||||
}
|
||||
}
|
||||
if artifacts.is_empty() {
|
||||
warn!("No artifacts found for {}", release.tag_name);
|
||||
continue;
|
||||
}
|
||||
let version = match Version::parse(if release.tag_name.starts_with("v") {
|
||||
&release.tag_name[1..]
|
||||
} else {
|
||||
&release.tag_name
|
||||
}) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Could not load version for release {} {}",
|
||||
release.tag_name, e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
releases.push(RepoRelease {
|
||||
version,
|
||||
version: Version::parse(if release.tag_name.starts_with("v") {
|
||||
&release.tag_name[1..]
|
||||
} else {
|
||||
&release.tag_name
|
||||
})?,
|
||||
description: Some(release.body),
|
||||
url: Some(release.url),
|
||||
artifacts,
|
||||
});
|
||||
|
||||
//TODO: handle more than one release
|
||||
break;
|
||||
}
|
||||
Ok(releases)
|
||||
}
|
||||
|
@ -94,6 +94,16 @@ impl TryInto<EventBuilder> for RepoArtifact {
|
||||
])?);
|
||||
}
|
||||
}
|
||||
ApkSignatureBlock::Nostr {
|
||||
signature,
|
||||
public_key,
|
||||
} => {
|
||||
b = b.tag(Tag::parse([
|
||||
"apk_nostr_signature",
|
||||
&hex::encode(public_key),
|
||||
&hex::encode(signature),
|
||||
])?);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(vn) = manifest.version_name {
|
||||
@ -115,7 +125,6 @@ impl TryInto<EventBuilder> for RepoArtifact {
|
||||
])?);
|
||||
}
|
||||
}
|
||||
ArtifactMetadata::Empty => {}
|
||||
}
|
||||
Ok(b)
|
||||
}
|
||||
@ -123,7 +132,6 @@ impl TryInto<EventBuilder> for RepoArtifact {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ArtifactMetadata {
|
||||
Empty,
|
||||
APK {
|
||||
manifest: AndroidManifest,
|
||||
signature_blocks: Vec<ApkSignatureBlock>,
|
||||
@ -150,7 +158,6 @@ impl Display for ArtifactMetadata {
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
ArtifactMetadata::Empty => write!(f, "empty"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -163,7 +170,6 @@ pub enum Platform {
|
||||
Windows { arch: Architecture },
|
||||
Linux { arch: Architecture },
|
||||
Web,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Display for Platform {
|
||||
@ -219,7 +225,6 @@ impl Display for Platform {
|
||||
}
|
||||
),
|
||||
Platform::Web => write!(f, "web"),
|
||||
Platform::Unknown => write!(f, "unknown"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -319,21 +324,6 @@ impl RepoRelease {
|
||||
ret.push(b.sign(signer).await?);
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Download and prepare release artifacts
|
||||
pub async fn load_artifacts(&mut self) -> Result<()> {
|
||||
let mut new_artifacts = vec![];
|
||||
for a in &self.artifacts {
|
||||
if let RepoResource::Remote(url) = &a.location {
|
||||
match load_artifact_url(&url).await {
|
||||
Ok(artifact) => new_artifacts.push(artifact),
|
||||
Err(e) => warn!("Failed to load artifact {}: {}", &url, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
self.artifacts = new_artifacts;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic artifact repository
|
||||
@ -364,6 +354,7 @@ impl TryInto<Box<dyn Repo>> for &Manifest {
|
||||
async fn load_artifact_url(url: &str) -> Result<RepoArtifact> {
|
||||
info!("Downloading artifact {}", url);
|
||||
let u = Url::parse(url)?;
|
||||
let rsp = reqwest::get(u.clone()).await?;
|
||||
let id = hex::encode(Sha256::digest(url.as_bytes()));
|
||||
let mut tmp = temp_dir().join(id);
|
||||
tmp.set_extension(
|
||||
@ -374,7 +365,6 @@ async fn load_artifact_url(url: &str) -> Result<RepoArtifact> {
|
||||
.unwrap(),
|
||||
);
|
||||
if !tmp.exists() {
|
||||
let rsp = reqwest::get(u.clone()).await?;
|
||||
let mut tmp_file = tokio::fs::File::create(&tmp).await?;
|
||||
let mut rsp_stream = rsp.bytes_stream();
|
||||
while let Some(data) = rsp_stream.next().await {
|
||||
|
Reference in New Issue
Block a user