Repository navigation
Expand file tree
/
Copy pathbackup_cmd.go
More file actions
147 lines (141 loc) · 3.74 KB
/
Copy pathbackup_cmd.go
File metadata and controls
147 lines (141 loc) · 3.74 KB
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
package main
import (
"bufio"
"errors"
"fmt"
"os"
"strings"
"golang.org/x/term"
"github.com/yetone/magpie/internal/backup"
"github.com/yetone/magpie/internal/edit"
)
// backupCmd writes every provider, setting, profile and agent model to one
// file sealed with a passphrase: magpie backup [--no-keys] [file].
func backupCmd(args []string) error {
keys, file := true, ""
for _, a := range args {
switch {
case a == "--no-keys":
keys = false
case strings.HasPrefix(a, "-"):
return fmt.Errorf("unknown flag %s (magpie backup [--no-keys] [file])", a)
case file == "":
file = a
default:
return errors.New("usage: magpie backup [--no-keys] [file]")
}
}
if file == "" {
file = "magpie" + backup.Ext
}
b, err := backup.Collect(keys, version)
if err != nil {
return err
}
pass, err := passphrase("Passphrase for the backup: ", true)
if err != nil {
return err
}
data, err := backup.Seal(b, pass)
if err != nil {
return err
}
if err := edit.WriteAtomic(file, data); err != nil {
return err
}
os.Chmod(file, 0o600)
what := "with their keys"
if !keys {
what = "without keys"
}
fmt.Printf("%s %s: %d providers %s, %d profiles, %d agent settings\n",
green.Render("saved"), file, len(b.Providers), what, len(b.Profiles), len(b.Agents))
fmt.Println(muted.Render("Subscriptions are not in it: sign in to them on the other machine. Keep the passphrase: without it the file can't be opened."))
return nil
}
// restoreCmd puts a backup in: magpie restore [--no-agents] <file>.
func restoreCmd(args []string) error {
parts, file := backup.All, ""
for _, a := range args {
switch {
case a == "--no-agents":
parts.Agents = false
case strings.HasPrefix(a, "-"):
return fmt.Errorf("unknown flag %s (magpie restore [--no-agents] <file>)", a)
case file == "":
file = a
default:
return errors.New("usage: magpie restore [--no-agents] <file>")
}
}
if file == "" {
return errors.New("usage: magpie restore [--no-agents] <file>")
}
data, err := os.ReadFile(file)
if err != nil {
return err
}
pass, err := passphrase("Passphrase: ", false)
if err != nil {
return err
}
b, err := backup.Open(data, pass)
if err != nil {
return err
}
r, err := backup.Restore(b, parts)
if err != nil {
return err
}
fmt.Printf("%s providers: %d added, %d replaced", green.Render("restored"), r.Added, r.Replaced)
if r.Settings {
fmt.Print("; settings")
}
fmt.Printf("; %d profiles; %d agent settings changed\n", r.Profiles, r.Agents)
if len(r.NeedKey) > 0 {
fmt.Println(muted.Render("Needs a key (magpie provider key <id>): " + strings.Join(r.NeedKey, ", ")))
}
if len(r.Skipped) > 0 {
fmt.Println(muted.Render("Left as they are (agent not here, or its model can't be reached yet): " + strings.Join(r.Skipped, ", ")))
}
return nil
}
// passphrase reads one from the terminal without echo, asked twice when
// it is being set; piped in, it is the first line of stdin.
func passphrase(prompt string, confirm bool) (string, error) {
fd := int(os.Stdin.Fd())
if !term.IsTerminal(fd) {
line, err := bufio.NewReader(os.Stdin).ReadString('\n')
line = strings.TrimRight(line, "\r\n")
if line == "" {
if err != nil {
return "", fmt.Errorf("no passphrase on stdin: %w", err)
}
return "", errors.New("the passphrase is empty")
}
return line, nil
}
read := func(p string) (string, error) {
fmt.Fprint(os.Stderr, p)
b, err := term.ReadPassword(fd)
fmt.Fprintln(os.Stderr)
return string(b), err
}
pass, err := read(prompt)
if err != nil {
return "", err
}
if pass == "" {
return "", errors.New("the passphrase is empty")
}
if confirm {
again, err := read("Again: ")
if err != nil {
return "", err
}
if again != pass {
return "", errors.New("the passphrases differ")
}
}
return pass, nil
}