use crate::errors::*;
use crate::p2p;
use crate::p2p::proto::{PeerAddr, PeerFilter};
use clap::{ArgAction, CommandFactory, Parser, Subcommand};
use clap_complete::Shell;
use std::ffi::OsString;
use std::io::stdout;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::pin::Pin;
use tokio::fs::File;
use tokio::io;
use tokio::io::AsyncRead;
#[derive(Debug, Clone)]
pub enum FileOrStdin {
File(PathBuf),
Stdin,
}
impl FileOrStdin {
pub fn default_stdin(list: &mut Vec<Self>) {
if list.is_empty() {
list.push(Self::Stdin);
}
}
pub async fn open(&self) -> Result<OpenFileOrStdin> {
match self {
Self::File(path) => {
debug!("Opening file {path:?}...");
let file = File::open(&path)
.await
.with_context(|| anyhow!("Failed to open file at path: {path:?}"))?;
Ok(OpenFileOrStdin::File(file))
}
Self::Stdin => Ok(OpenFileOrStdin::Stdin(io::stdin())),
}
}
}
impl From<OsString> for FileOrStdin {
fn from(s: OsString) -> Self {
if s.to_str() == Some("-") {
Self::Stdin
} else {
Self::File(s.into())
}
}
}
#[derive(Debug)]
pub enum OpenFileOrStdin {
File(File),
Stdin(io::Stdin),
}
impl AsyncRead for OpenFileOrStdin {
fn poll_read(
self: std::pin::Pin<&mut Self>,
ctx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::result::Result<(), std::io::Error>> {
match self.get_mut() {
Self::File(file) => Pin::new(file).poll_read(ctx, buf),
Self::Stdin(stdin) => Pin::new(stdin).poll_read(ctx, buf),
}
}
}
#[derive(Debug, Parser)]
#[command(version)]
pub struct Args {
#[arg(short, long, global = true, action(ArgAction::Count))]
pub verbose: u8,
#[arg(short, long, global = true, action(ArgAction::Count))]
pub quiet: u8,
#[arg(short, long, global = true)]
pub config: Option<PathBuf>,
#[arg(long, global = true)]
pub proxy: Option<SocketAddr>,
#[arg(long, global = true, env = "APT_SWARM_DATA_PATH")]
pub data_path: Option<PathBuf>,
#[arg(short = 'C', long, global = true)]
pub colors: bool,
#[command(subcommand)]
pub subcommand: SubCommand,
}
#[derive(Debug, Subcommand)]
pub enum SubCommand {
Import(Import),
Export(Export),
Fetch(Fetch),
Latest(Latest),
Ls(Ls),
Keyring(Keyring),
Pull(Pull),
P2p(P2p),
#[command(subcommand)]
Plumbing(Plumbing),
}
#[derive(Debug, Parser)]
pub struct Import {
pub paths: Vec<FileOrStdin>,
}
#[derive(Debug, Parser)]
pub struct Export {
pub release_hashes: Vec<String>,
#[arg(long)]
pub scan: bool,
}
#[derive(Debug, Parser)]
pub struct Fetch {
#[arg(short = 'j', long)]
pub concurrency: Option<usize>,
pub fingerprints: Vec<sequoia_openpgp::Fingerprint>,
}
#[derive(Debug, Parser)]
pub struct Latest {
pub fingerprint: sequoia_openpgp::Fingerprint,
#[arg(short = 'K', long, group = "print")]
pub key: bool,
#[arg(short = 'D', long, group = "print")]
pub date: bool,
#[arg(short = 'B', long, group = "print")]
pub body: bool,
#[arg(short = 'H', long, group = "print")]
pub header: bool,
#[arg(short = 'A', long, group = "print")]
pub attachment: bool,
#[arg(short = 'F', long)]
pub allow_future_dates: bool,
}
#[derive(Debug, Parser)]
pub struct Ls {
pub prefix: Option<String>,
#[arg(short = 's', long)]
pub count: bool,
}
#[derive(Debug, Parser)]
pub struct Keyring {
#[arg(long)]
pub json: bool,
#[arg(short, long)]
pub stats: bool,
}
#[derive(Debug, Parser)]
pub struct Pull {
pub addr: PeerAddr,
#[arg(long = "key")]
pub keys: Vec<sequoia_openpgp::Fingerprint>,
#[arg(short = 'n', long)]
pub dry_run: bool,
}
#[derive(Debug, Parser)]
pub struct P2p {
#[cfg(feature = "irc")]
#[command(flatten)]
pub irc: P2pIrc,
#[command(flatten)]
pub dns: P2pDns,
#[arg(long)]
pub no_bootstrap: bool,
#[arg(long)]
pub no_fetch: bool,
#[arg(long)]
pub no_bind: bool,
#[arg(short = 'B', long, default_values = &["0.0.0.0:16169", "[::]:16169"])]
pub bind: Vec<SocketAddr>,
#[arg(short = 'A', long)]
pub announce: Vec<SocketAddr>,
#[arg(long, value_name = "IMAGE")]
pub check_container_updates: Option<String>,
#[arg(long, value_name = "COMMIT", env = "UPDATE_CHECK_COMMIT")]
pub update_assume_commit: Option<String>,
}
#[cfg(feature = "irc")]
#[derive(Debug, Parser)]
pub struct P2pIrc {
#[arg(long)]
pub no_irc: bool,
#[arg(long, default_value = "ircs://irc.hackint.org/##apt-swarm-p2p")]
pub irc_channel: String,
}
#[derive(Debug, Parser)]
pub struct P2pDns {
#[arg(long)]
pub no_dns: bool,
#[arg(long, default_values = p2p::dns::DNS_SEEDS)]
pub dns: Vec<String>,
}
#[derive(Debug, Subcommand)]
pub enum Plumbing {
AttachSig(AttachSig),
Canonicalize(Canonicalize),
Completions(Completions),
Config(Config),
ContainerUpdateCheck(ContainerUpdateCheck),
#[cfg(unix)]
DbServer(DbServer),
DnsBootstrap(DnsBootstrap),
Fetch(PlumbingFetch),
Fingerprint(Fingerprint),
Fsck(Fsck),
Index(Index),
Migrate(Migrate),
Paths(Paths),
PeerdbAdd(PeerdbAdd),
PeerdbList(PeerdbList),
PeerdbGc(PeerdbGc),
SyncPull(SyncPull),
SyncYield(SyncYield),
}
#[derive(Debug, Parser)]
pub struct AttachSig {
pub content: PathBuf,
pub signatures: Vec<PathBuf>,
}
#[derive(Debug, Parser)]
pub struct Canonicalize {
pub paths: Vec<FileOrStdin>,
#[arg(long)]
pub verify: bool,
}
#[derive(Debug, Parser)]
pub struct Completions {
pub shell: Shell,
}
impl Completions {
pub fn generate(&self) -> Result<()> {
clap_complete::generate(self.shell, &mut Args::command(), "apt-swarm", &mut stdout());
Ok(())
}
}
#[derive(Debug, Parser)]
pub struct Config {}
#[derive(Debug, Parser)]
pub struct ContainerUpdateCheck {
#[arg(long)]
pub image: String,
#[arg(long, env = "UPDATE_CHECK_COMMIT")]
pub commit: String,
}
#[derive(Debug, Parser)]
pub struct DbServer {}
#[derive(Debug, Parser)]
pub struct DnsBootstrap {
#[arg(short = '4', long, group = "proto")]
pub ipv4_only: bool,
#[arg(short = '6', long, group = "proto")]
pub ipv6_only: bool,
#[arg(default_values = p2p::dns::DNS_SEEDS)]
pub dns: Vec<String>,
}
#[derive(Debug, Parser)]
pub struct PlumbingFetch {
pub url: String,
}
#[derive(Debug, Parser)]
pub struct Fingerprint {
pub paths: Vec<FileOrStdin>,
}
#[derive(Debug, Parser)]
pub struct Fsck {
pub prefix: Option<String>,
}
#[derive(Debug, Parser)]
pub struct Index {
pub fingerprint: sequoia_openpgp::Fingerprint,
pub hash_algo: String,
pub prefix: Option<String>,
#[arg(short, long)]
pub batch: bool,
}
#[derive(Debug, Parser)]
pub struct Migrate {}
#[derive(Debug, Parser)]
pub struct Paths {}
#[derive(Debug, Parser)]
pub struct PeerdbAdd {
pub addrs: Vec<PeerAddr>,
}
#[derive(Debug, Parser)]
pub struct PeerdbList {
pub filters: Vec<PeerFilter>,
}
#[derive(Debug, Parser)]
pub struct PeerdbGc {}
#[derive(Debug, Parser)]
pub struct SyncPull {
pub keys: Vec<sequoia_openpgp::Fingerprint>,
#[arg(short = 'n', long)]
pub dry_run: bool,
}
#[derive(Debug, Parser)]
pub struct SyncYield {
#[arg(long)]
pub pex: bool,
}