~core/sage

ref: a5ea94fd04873a203d0a5e2066ca4177c5102c93 sage/src/main.rs -rw-r--r-- 5.2 KiB
a5ea94fd — core feat: better err msg 21 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use crate::cmds::createid::create_id;
use crate::db::db_default;
use crate::wrapper::wrapper;
use std::process::exit;

mod cmds;
mod db;
mod identity;
mod wrapper;

fn main() -> anyhow::Result<()> {
    let mut pargs = pico_args::Arguments::from_env();

    if pargs.contains(["-h", "--help"]) {
        print_help();
        exit(0);
    }

    let database = pargs
        .opt_value_from_str(["-D", "--database"])?
        .unwrap_or(db_default());

    let Some(subcommand) = pargs.subcommand()? else {
        // run the wrapper
        return wrapper(pargs, &database);
    };

    match subcommand.as_str() {
        "create-id" => {
            let Ok(name) = pargs.free_from_str() else {
                eprintln!("name is required, --help for help");
                exit(1);
            };
            let Ok(email) = pargs.free_from_str() else {
                eprintln!("email is required, --help for help");
                exit(1);
            };

            create_id(&database, name, email)?;
        }
        "ls" => {
            cmds::ls::list_keys(
                &database,
                pargs.contains(["-l", "--local"]),
                pargs.contains(["-p", "--peer"]),
                pargs.contains(["-f", "--full"]),
            )?;
        }
        "show" => {
            let Ok(search) = pargs.free_from_str() else {
                eprintln!("search term is required, --help for help");
                exit(1);
            };
            cmds::show::show_key(&database, search, pargs.contains(["-e", "--expose-secret"]))?;
        }
        "import" => {
            let Ok(key) = pargs.free_from_str() else {
                eprintln!("key is required, --help for help");
                exit(1);
            };
            cmds::import::import_key(&database, key, pargs.contains(["-i", "--import-anyway"]))?;
        }
        unknown => {
            eprintln!("Unknown subcommand: {}", unknown);
            print_help();
            exit(1);
        }
    }

    Ok(())
}

fn print_help() {
    let cf = db_default().display().to_string();
    println!("{}", HELP.to_string().replace("{CONFIGFILE}", cf.as_str()));
}
const HELP: &str = "\
sage - simple age wrapper for named identities

USAGE:
    sage [OPTIONS] [SUBCOMMAND]
    sage [--encrypt] (-r RECIPIENT | -R PATH)... [--armor] [-o OUTPUT] [INPUT]
    sage [--encrypt] --passphrase [--armor] [-o OUTPUT] [INPUT]
    sage --decrypt [-i PATH]... [-o OUTPUT] [INPUT]

OPTIONS:
    -h, --help                  Prints help information
    -D, --database <FILE>       Set the database file (default {CONFIGFILE})

    -v, --verbose               Enable debug logging
    -e, --encrypt               Encrypt the input to the output. Default if omitted.
    -d, --decrypt               Decrypt the input to the output.
    -o, --output OUTPUT         Write the result to the file at path OUTPUT.
    -a, --armor                 Encrypt to a PEM encoded format.
    -p, --passphrase            Encrypt with a passphrase.
    -r, --recipient RECIPIENT   Encrypt to the specified RECIPIENT. Can be repeated.
    -R, --recipients-file PATH  Encrypt to the recipients listed at PATH. Can be repeated.
    -i, --identity IDENTITY     Use the specified identity. Can be repeated.

INPUT defaults to standard input, and OUTPUT defaults to standard output.
If OUTPUT exists, it will be overwritten.

RECIPIENT can be an age public key generate by age-keygen (\"age1...\")
an SSH public key (\"ssh-ed25519 AAAA...\", \"ssh-rsa AAAA...\"), OR
a fuzzy search term to use the database to locate a key.

Recipient files contain one or more recipients, one per line. Empty lines
and lines starting with \"#\" are ignored as comments. \"-\" may be uised to
read recipients from standard input.

An IDENTITY may either be a fuzzy search term to search the database, or
an identity file.

Identity files contain one or more secret keys (\"AGE-SECRET-KEY-1...\"),
one per line, or an SSH key. Empty lines and lines starting with \"#\" are
ignored as comments. Passphrase encrypted age files can be used as
identity files. Multiple key files can be provided, and any unused ones
will be ignored. \"-\" may be used to read identities from standard input.

When --encrypt is specified explicitly, -i can also be used to encrypt to an
identity file symmetrically, instead or in addition to normal recipients.

If an identity is omitted and only one local identity exists in the database,
it will be chosen automatically.

SUBCOMMANDS:

create-id <name> <email>
    Create a new local identity in the database.
    <name>      Name for the new identity
    <email>     Email of the new identity

ls [OPTIONS]
    List keys in the database
    OPTIONS:

    -l, --local     Show only local (your) keys
    -p, --peer      Show only peer (other's) keys
    -f, --full      List full public keys instead of key IDs

show [OPTIONS] <search term>
    Search the database (fuzzy) and show a specific key

    OPTIONS:
    -e, --expose-secret     Output the secret key, if it is a local key

import [OPTIONS] <key>
    Imports the provided key into the database.
    If the keyid already exists, it will do nothing.

    OPTIONS:
    -i, --insert-anyway     Insert even if a matching identity is already present
";