apt_swarm/
args.rs

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    /// If the given list is empty, select stdin as input
25    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    /// Increase logging output (can be used multiple times)
78    #[arg(short, long, global = true, action(ArgAction::Count))]
79    pub verbose: u8,
80    /// Reduce logging output (can be used multiple times)
81    #[arg(short, long, global = true, action(ArgAction::Count))]
82    pub quiet: u8,
83    /// Path to config file to use
84    #[arg(short, long, global = true)]
85    pub config: Option<PathBuf>,
86    /// Configure a socks5 proxy for outgoing connections
87    #[arg(long, global = true)]
88    pub proxy: Option<SocketAddr>,
89    /// Configure the path where persistent data should be stored
90    #[arg(long, global = true, env = "APT_SWARM_DATA_PATH")]
91    pub data_path: Option<PathBuf>,
92    /// Always enable colored output
93    #[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/// Import signed InRelease files
114#[derive(Debug, Parser)]
115pub struct Import {
116    /// The input files to read (- for stdin)
117    pub paths: Vec<FileOrStdin>,
118}
119
120/// Export all known InRelease files
121#[derive(Debug, Parser)]
122pub struct Export {
123    pub release_hashes: Vec<String>,
124    /// Instead of exact matches, scan with the given prefix(es)
125    #[arg(long)]
126    pub scan: bool,
127}
128
129/// Fetch the latest InRelease files and import them
130#[derive(Debug, Parser)]
131pub struct Fetch {
132    /// Number of concurrent requests
133    #[arg(short = 'j', long)]
134    pub concurrency: Option<usize>,
135}
136
137/// Query the latest release for a given key
138#[derive(Debug, Parser)]
139pub struct Latest {
140    /// The signing key to query
141    pub fingerprint: sequoia_openpgp::Fingerprint,
142    /// Print only the key
143    #[arg(short = 'K', long, group = "print")]
144    pub key: bool,
145    /// Print only the date
146    #[arg(short = 'D', long, group = "print")]
147    pub date: bool,
148    /// Print only the body (with the signature stripped)
149    #[arg(short = 'B', long, group = "print")]
150    pub body: bool,
151    /// Print only the header (everything before the first empty line)
152    #[arg(short = 'H', long, group = "print")]
153    pub header: bool,
154    /// Print only the attachment (everything after the first empty line)
155    #[arg(short = 'A', long, group = "print")]
156    pub attachment: bool,
157    /// Permit dates from the future
158    #[arg(short = 'F', long)]
159    pub allow_future_dates: bool,
160}
161
162/// List hashes of all known releases
163#[derive(Debug, Parser)]
164pub struct Ls {
165    /// Use a specific prefix to filter by
166    pub prefix: Option<String>,
167    /// Count keys present in database instead of listing them
168    #[arg(short = 's', long)]
169    pub count: bool,
170}
171
172/// List all keys currently configured for monitoring
173#[derive(Debug, Parser)]
174pub struct Keyring {
175    /// Output keyring as json
176    #[arg(long)]
177    pub json: bool,
178    /// Show the number of known signatures for a given subkey
179    #[arg(short, long)]
180    pub stats: bool,
181}
182
183/// Connect to a remote node and sync from them
184#[derive(Debug, Parser)]
185pub struct Pull {
186    /// The address to connect to
187    pub addr: PeerAddr,
188    /// Only sync data for specific keys, identified by their fingerprint
189    #[arg(long = "key")]
190    pub keys: Vec<sequoia_openpgp::Fingerprint>,
191    /// Run the sync but do not import
192    #[arg(short = 'n', long)]
193    pub dry_run: bool,
194}
195
196/// Run in p2p swarm mode
197#[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    /// Do not use any bootstrapping mechanism, initial peers need to be added manually
205    #[arg(long)]
206    pub no_bootstrap: bool,
207    /// Do not actively fetch updates from the configured repositories
208    #[arg(long)]
209    pub no_fetch: bool,
210    /// Do not bind a sync port for p2p traffic
211    #[arg(long)]
212    pub no_bind: bool,
213    /// The addresses to bind a sync port for p2p traffic (if not disabled)
214    #[arg(short = 'B', long, default_values = &["0.0.0.0:16169", "[::]:16169"])]
215    pub bind: Vec<SocketAddr>,
216    /// Configure addresses to announce if somebody wants to sync from us
217    #[arg(short = 'A', long)]
218    pub announce: Vec<SocketAddr>,
219    /// Monitor a container registry for updates and terminate if an update is available (eg. ghcr.io/kpcyrd/apt-swarm:edge)
220    #[arg(long, value_name = "IMAGE")]
221    pub check_container_updates: Option<String>,
222    /// The VCS commit to assume for our currently running image
223    #[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    /// Do not connect to irc for peer discovery
231    #[arg(long)]
232    pub no_irc: bool,
233    /// The irc server and channel to connect to
234    #[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    /// Do not query any configured dnsseeds
241    #[arg(long)]
242    pub no_dns: bool,
243    /// The dns names to query for bootstrapping
244    #[arg(long, default_values = p2p::dns::DNS_SEEDS)]
245    pub dns: Vec<String>,
246}
247
248/// Access to low-level features
249#[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/// Create a clear-signed document from a detached signature
277#[derive(Debug, Parser)]
278pub struct AttachSig {
279    pub content: PathBuf,
280    pub signatures: Vec<PathBuf>,
281}
282
283/// Transform a signed InRelease file into a canonical representation
284#[derive(Debug, Parser)]
285pub struct Canonicalize {
286    /// The input files to read (- for stdin)
287    pub paths: Vec<FileOrStdin>,
288    /// Verify signatures belong to trusted key in keyring
289    #[arg(long)]
290    pub verify: bool,
291}
292
293/// Generate shell completions
294#[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/// Print applied configuration
307#[derive(Debug, Parser)]
308pub struct Config {}
309
310/// Query a container registry for a more recent release of a given image
311#[derive(Debug, Parser)]
312pub struct ContainerUpdateCheck {
313    /// The image to monitor for updates (eg. ghcr.io/kpcyrd/apt-swarm:edge)
314    #[arg(long)]
315    pub image: String,
316    /// The commit to assume for our currently running image
317    #[arg(long, env = "UPDATE_CHECK_COMMIT")]
318    pub commit: String,
319}
320
321/// Bind a unix domain socket and allow abstract database access from multiple processes
322#[derive(Debug, Parser)]
323pub struct DbServer {}
324
325/// Run dns bootstrap query, print results
326#[derive(Debug, Parser)]
327pub struct DnsBootstrap {
328    /// Only print ipv4 records
329    #[arg(short = '4', long, group = "proto")]
330    pub ipv4_only: bool,
331    /// Only print ipv6 records
332    #[arg(short = '6', long, group = "proto")]
333    pub ipv6_only: bool,
334    /// The dns name to query
335    #[arg(default_values = p2p::dns::DNS_SEEDS)]
336    pub dns: Vec<String>,
337}
338
339/// Fetch a link and write response to stdout
340#[derive(Debug, Parser)]
341pub struct PlumbingFetch {
342    /// The resource to fetch
343    pub url: String,
344}
345
346/// Extract the fingerprint of a pgp key
347#[derive(Debug, Parser)]
348pub struct Fingerprint {
349    /// The input files to read (- for stdin)
350    pub paths: Vec<FileOrStdin>,
351}
352
353/// Verify stored objects
354#[derive(Debug, Parser)]
355pub struct Fsck {
356    pub prefix: Option<String>,
357}
358
359#[cfg(feature = "git")]
360/// Convert signed git objects into signature format used by apt-swarm
361#[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/// Attempt to export all signed objects from a git repo
370#[derive(Debug, Parser)]
371pub struct GitScrape {
372    pub paths: Vec<PathBuf>,
373}
374
375/// Scan the database and calculate the requested index
376#[derive(Debug, Parser)]
377pub struct Index {
378    /// The signing key to index
379    pub fingerprint: sequoia_openpgp::Fingerprint,
380    /// Only entries with this hash algorithm
381    pub hash_algo: String,
382    /// Calculate an index based on a specific prefix
383    pub prefix: Option<String>,
384    /// Calculate a batch index, they are bigger but allow syncs with fewer round-trips
385    #[arg(short, long)]
386    pub batch: bool,
387}
388
389/// Open a fresh database and re-import the old data
390#[derive(Debug, Parser)]
391pub struct Migrate {}
392
393/// Print configured paths
394#[derive(Debug, Parser)]
395pub struct Paths {}
396
397/// Add a peerdb entry
398#[derive(Debug, Parser)]
399pub struct PeerdbAdd {
400    pub addrs: Vec<PeerAddr>,
401}
402
403/// Read and print peerdb file
404#[derive(Debug, Parser)]
405pub struct PeerdbList {
406    /// Filter by ip address or network (without port)
407    pub filters: Vec<PeerFilter>,
408}
409
410/// Remove old peerdb entries
411#[derive(Debug, Parser)]
412pub struct PeerdbGc {}
413
414/// Fetch all available signatures over stdio (use with sync-yield)
415#[derive(Debug, Parser)]
416pub struct SyncPull {
417    pub keys: Vec<sequoia_openpgp::Fingerprint>,
418    /// Run the sync but do not import
419    #[arg(short = 'n', long)]
420    pub dry_run: bool,
421}
422
423/// Provide access to our signatures over stdio (use with sync-pull)
424#[derive(Debug, Parser)]
425pub struct SyncYield {
426    /// Enable access to peer-exchange queries
427    #[arg(long)]
428    pub pex: bool,
429}