1use crate::errors::*;
2use crate::p2p;
3use crate::p2p::proto::{PeerAddr, PeerFilter};
4#[cfg(feature = "git")]
5use crate::plumbing;
6use clap::{ArgAction, CommandFactory, Parser, Subcommand};
7use clap_complete::Shell;
8use std::ffi::OsString;
9use std::io::stdout;
10use std::net::SocketAddr;
11use std::path::PathBuf;
12use std::pin::Pin;
13use tokio::fs::File;
14use tokio::io;
15use tokio::io::AsyncRead;
16
17#[derive(Debug, Clone)]
18pub enum FileOrStdin {
19 File(PathBuf),
20 Stdin,
21}
22
23impl FileOrStdin {
24 pub fn default_stdin(list: &mut Vec<Self>) {
26 if list.is_empty() {
27 list.push(Self::Stdin);
28 }
29 }
30
31 pub async fn open(&self) -> Result<OpenFileOrStdin> {
32 match self {
33 Self::File(path) => {
34 debug!("Opening file {path:?}...");
35 let file = File::open(&path)
36 .await
37 .with_context(|| anyhow!("Failed to open file at path: {path:?}"))?;
38 Ok(OpenFileOrStdin::File(file))
39 }
40 Self::Stdin => Ok(OpenFileOrStdin::Stdin(io::stdin())),
41 }
42 }
43}
44
45impl From<OsString> for FileOrStdin {
46 fn from(s: OsString) -> Self {
47 if s.to_str() == Some("-") {
48 Self::Stdin
49 } else {
50 Self::File(s.into())
51 }
52 }
53}
54
55#[derive(Debug)]
56pub enum OpenFileOrStdin {
57 File(File),
58 Stdin(io::Stdin),
59}
60
61impl AsyncRead for OpenFileOrStdin {
62 fn poll_read(
63 self: std::pin::Pin<&mut Self>,
64 ctx: &mut std::task::Context<'_>,
65 buf: &mut tokio::io::ReadBuf<'_>,
66 ) -> std::task::Poll<std::result::Result<(), std::io::Error>> {
67 match self.get_mut() {
68 Self::File(file) => Pin::new(file).poll_read(ctx, buf),
69 Self::Stdin(stdin) => Pin::new(stdin).poll_read(ctx, buf),
70 }
71 }
72}
73
74#[derive(Debug, Parser)]
75#[command(version)]
76pub struct Args {
77 #[arg(short, long, global = true, action(ArgAction::Count))]
79 pub verbose: u8,
80 #[arg(short, long, global = true, action(ArgAction::Count))]
82 pub quiet: u8,
83 #[arg(short, long, global = true)]
85 pub config: Option<PathBuf>,
86 #[arg(long, global = true)]
88 pub proxy: Option<SocketAddr>,
89 #[arg(long, global = true, env = "APT_SWARM_DATA_PATH")]
91 pub data_path: Option<PathBuf>,
92 #[arg(short = 'C', long, global = true)]
94 pub colors: bool,
95 #[command(subcommand)]
96 pub subcommand: SubCommand,
97}
98
99#[derive(Debug, Subcommand)]
100pub enum SubCommand {
101 Import(Import),
102 Export(Export),
103 Fetch(Fetch),
104 Latest(Latest),
105 Ls(Ls),
106 Keyring(Keyring),
107 Pull(Pull),
108 P2p(P2p),
109 #[command(subcommand)]
110 Plumbing(Plumbing),
111}
112
113#[derive(Debug, Parser)]
115pub struct Import {
116 pub paths: Vec<FileOrStdin>,
118}
119
120#[derive(Debug, Parser)]
122pub struct Export {
123 pub release_hashes: Vec<String>,
124 #[arg(long)]
126 pub scan: bool,
127}
128
129#[derive(Debug, Parser)]
131pub struct Fetch {
132 #[arg(short = 'j', long)]
134 pub concurrency: Option<usize>,
135}
136
137#[derive(Debug, Parser)]
139pub struct Latest {
140 pub fingerprint: sequoia_openpgp::Fingerprint,
142 #[arg(short = 'K', long, group = "print")]
144 pub key: bool,
145 #[arg(short = 'D', long, group = "print")]
147 pub date: bool,
148 #[arg(short = 'B', long, group = "print")]
150 pub body: bool,
151 #[arg(short = 'H', long, group = "print")]
153 pub header: bool,
154 #[arg(short = 'A', long, group = "print")]
156 pub attachment: bool,
157 #[arg(short = 'F', long)]
159 pub allow_future_dates: bool,
160}
161
162#[derive(Debug, Parser)]
164pub struct Ls {
165 pub prefix: Option<String>,
167 #[arg(short = 's', long)]
169 pub count: bool,
170}
171
172#[derive(Debug, Parser)]
174pub struct Keyring {
175 #[arg(long)]
177 pub json: bool,
178 #[arg(short, long)]
180 pub stats: bool,
181}
182
183#[derive(Debug, Parser)]
185pub struct Pull {
186 pub addr: PeerAddr,
188 #[arg(long = "key")]
190 pub keys: Vec<sequoia_openpgp::Fingerprint>,
191 #[arg(short = 'n', long)]
193 pub dry_run: bool,
194}
195
196#[derive(Debug, Parser)]
198pub struct P2p {
199 #[cfg(feature = "irc")]
200 #[command(flatten)]
201 pub irc: P2pIrc,
202 #[command(flatten)]
203 pub dns: P2pDns,
204 #[arg(long)]
206 pub no_bootstrap: bool,
207 #[arg(long)]
209 pub no_fetch: bool,
210 #[arg(long)]
212 pub no_bind: bool,
213 #[arg(short = 'B', long, default_values = &["0.0.0.0:16169", "[::]:16169"])]
215 pub bind: Vec<SocketAddr>,
216 #[arg(short = 'A', long)]
218 pub announce: Vec<SocketAddr>,
219 #[arg(long, value_name = "IMAGE")]
221 pub check_container_updates: Option<String>,
222 #[arg(long, value_name = "COMMIT", env = "UPDATE_CHECK_COMMIT")]
224 pub update_assume_commit: Option<String>,
225}
226
227#[cfg(feature = "irc")]
228#[derive(Debug, Parser)]
229pub struct P2pIrc {
230 #[arg(long)]
232 pub no_irc: bool,
233 #[arg(long, default_value = "ircs://irc.hackint.org/##apt-swarm-p2p")]
235 pub irc_channel: String,
236}
237
238#[derive(Debug, Parser)]
239pub struct P2pDns {
240 #[arg(long)]
242 pub no_dns: bool,
243 #[arg(long, default_values = p2p::dns::DNS_SEEDS)]
245 pub dns: Vec<String>,
246}
247
248#[derive(Debug, Subcommand)]
250pub enum Plumbing {
251 AttachSig(AttachSig),
252 Canonicalize(Canonicalize),
253 Completions(Completions),
254 Config(Config),
255 ContainerUpdateCheck(ContainerUpdateCheck),
256 #[cfg(unix)]
257 DbServer(DbServer),
258 DnsBootstrap(DnsBootstrap),
259 Fetch(PlumbingFetch),
260 Fingerprint(Fingerprint),
261 Fsck(Fsck),
262 #[cfg(feature = "git")]
263 GitObject(GitObject),
264 #[cfg(feature = "git")]
265 GitScrape(GitScrape),
266 Index(Index),
267 Migrate(Migrate),
268 Paths(Paths),
269 PeerdbAdd(PeerdbAdd),
270 PeerdbList(PeerdbList),
271 PeerdbGc(PeerdbGc),
272 SyncPull(SyncPull),
273 SyncYield(SyncYield),
274}
275
276#[derive(Debug, Parser)]
278pub struct AttachSig {
279 pub content: PathBuf,
280 pub signatures: Vec<PathBuf>,
281}
282
283#[derive(Debug, Parser)]
285pub struct Canonicalize {
286 pub paths: Vec<FileOrStdin>,
288 #[arg(long)]
290 pub verify: bool,
291}
292
293#[derive(Debug, Parser)]
295pub struct Completions {
296 pub shell: Shell,
297}
298
299impl Completions {
300 pub fn generate(&self) -> Result<()> {
301 clap_complete::generate(self.shell, &mut Args::command(), "apt-swarm", &mut stdout());
302 Ok(())
303 }
304}
305
306#[derive(Debug, Parser)]
308pub struct Config {}
309
310#[derive(Debug, Parser)]
312pub struct ContainerUpdateCheck {
313 #[arg(long)]
315 pub image: String,
316 #[arg(long, env = "UPDATE_CHECK_COMMIT")]
318 pub commit: String,
319}
320
321#[derive(Debug, Parser)]
323pub struct DbServer {}
324
325#[derive(Debug, Parser)]
327pub struct DnsBootstrap {
328 #[arg(short = '4', long, group = "proto")]
330 pub ipv4_only: bool,
331 #[arg(short = '6', long, group = "proto")]
333 pub ipv6_only: bool,
334 #[arg(default_values = p2p::dns::DNS_SEEDS)]
336 pub dns: Vec<String>,
337}
338
339#[derive(Debug, Parser)]
341pub struct PlumbingFetch {
342 pub url: String,
344}
345
346#[derive(Debug, Parser)]
348pub struct Fingerprint {
349 pub paths: Vec<FileOrStdin>,
351}
352
353#[derive(Debug, Parser)]
355pub struct Fsck {
356 pub prefix: Option<String>,
357}
358
359#[cfg(feature = "git")]
360#[derive(Debug, Parser)]
362pub struct GitObject {
363 pub paths: Vec<FileOrStdin>,
364 #[arg(short, long)]
365 pub kind: Option<plumbing::git::Kind>,
366}
367
368#[cfg(feature = "git")]
369#[derive(Debug, Parser)]
371pub struct GitScrape {
372 pub paths: Vec<PathBuf>,
373}
374
375#[derive(Debug, Parser)]
377pub struct Index {
378 pub fingerprint: sequoia_openpgp::Fingerprint,
380 pub hash_algo: String,
382 pub prefix: Option<String>,
384 #[arg(short, long)]
386 pub batch: bool,
387}
388
389#[derive(Debug, Parser)]
391pub struct Migrate {}
392
393#[derive(Debug, Parser)]
395pub struct Paths {}
396
397#[derive(Debug, Parser)]
399pub struct PeerdbAdd {
400 pub addrs: Vec<PeerAddr>,
401}
402
403#[derive(Debug, Parser)]
405pub struct PeerdbList {
406 pub filters: Vec<PeerFilter>,
408}
409
410#[derive(Debug, Parser)]
412pub struct PeerdbGc {}
413
414#[derive(Debug, Parser)]
416pub struct SyncPull {
417 pub keys: Vec<sequoia_openpgp::Fingerprint>,
418 #[arg(short = 'n', long)]
420 pub dry_run: bool,
421}
422
423#[derive(Debug, Parser)]
425pub struct SyncYield {
426 #[arg(long)]
428 pub pex: bool,
429}