Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: style and copy edits #1

Merged
merged 7 commits into from
Mar 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
# Melt

Backup and restore a SSH private key to mnemonic word set you can memorize.

## Usage

You check its `backup` and `restore` subcommands:
Backup and restore SSH private keys using memorizable seed phrases.

```shell
# Generate a seed phrase from an SSH key
melt ~/.ssh/id_ed25519
melt restore --mnemonic "words from backup" ./recovered_id_ed25519

# Rebuild the key from the seed phrase
melt restore ./my-key --seed "seed phrase"
```

You can also pipe to and from a file directly, e.g.:
You can also pipe to and from a file directly:

```shell
melt ~/.ssh/id_ed25519 > words
melt restore ./recovered_id_ed25519 < words
```

## How it works
## How it Works

It all comes down to the private key __seed__:

Expand Down Expand Up @@ -59,4 +58,6 @@ The key should be basically the same though.

Part of [Charm](https://charm.sh).

<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge-unrounded.jpg" width="400"></a>
<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>

Charm热爱开源 • Charm loves open source
104 changes: 83 additions & 21 deletions cmd/melt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,47 @@ import (
"fmt"
"io"
"os"
"strings"

"github.com/caarlos0/sshmarshal"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/melt"
"github.com/mattn/go-isatty"
"github.com/muesli/coral"
mcoral "github.com/muesli/mango-coral"
"github.com/muesli/reflow/wordwrap"
"github.com/muesli/roff"
"github.com/muesli/termenv"
"golang.org/x/crypto/ssh"
"golang.org/x/term"
)

const (
maxWidth = 72
)

var (
headerStyle = lipgloss.NewStyle().Italic(true)
mnemonicStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("63")).Margin(1).Width(60)
restoreStyle = lipgloss.NewStyle().Bold(true).Margin(1)
baseStyle = lipgloss.NewStyle().Margin(0, 0, 1, 2)
violet = lipgloss.Color(completeColor("#6B50FF", "63", "12"))
cmdStyle = lipgloss.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#FF5E8E", Dark: "#FF5E8E"}).
Background(lipgloss.AdaptiveColor{Light: completeColor("#ECECEC", "255", "7"), Dark: "#1F1F1F"}).
Padding(0, 1)
mnemonicStyle = baseStyle.Copy().
Foreground(violet).
Background(lipgloss.AdaptiveColor{Light: completeColor("#EEEBFF", "255", "7"), Dark: completeColor("#1B1731", "235", "8")}).
Padding(1, 2)
keyPathStyle = lipgloss.NewStyle().Foreground(violet)

rootCmd = &coral.Command{
Use: "melt",
Example: ` melt ~/.ssh/id_ed25519
melt ~/.ssh/id_ed25519 > mnemonic
melt restore --mnemonic \"list of words\" ./restored_id25519
melt restore ./restored_id25519 < mnemonic`,
Short: "Backup a SSH private key to a mnemonic set of keys",
Long: `melt uses bip39 to create a mnemonic set of words that represents your SSH keys.
You can then use those words to restore your private key at any time.`,
melt ~/.ssh/id_ed25519 > seed
melt restore --seed "seed phrase" ./restored_id25519
melt restore ./restored_id25519 < seed`,
Short: "Generate a seed phrase from an SSH key",
Long: `melt generates a seed phrase from an SSH key. That phrase can
be used to rebuild your public and private keys.`,
Args: coral.ExactArgs(1),
SilenceUsage: true,
RunE: func(cmd *coral.Command, args []string) error {
Expand All @@ -42,11 +56,34 @@ You can then use those words to restore your private key at any time.`,
return err
}
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Println(headerStyle.Render(`Success!!!
You can now use the words bellow to recreate your key using the 'melt restore' command.
Store them somewhere safe, print or memorize them.`))
fmt.Println(mnemonicStyle.Render(mnemonic))
b := strings.Builder{}
w := getWidth(maxWidth)

b.WriteRune('\n')
meltCmd := cmdStyle.Render(os.Args[0])
renderBlock(&b, baseStyle, w, fmt.Sprintf("OK! Your key has been melted down to the seed phrase below. Store it somewhere safe. You can use %s to recover your key at any time.", meltCmd))
renderBlock(&b, mnemonicStyle, w, mnemonic)
renderBlock(&b, baseStyle, w, "To recreate this key run:")

// Build formatted restore command
const cmdEOL = " \\"
cmd := wordwrap.String(
os.Args[0]+` restore ./my-key --seed "`+mnemonic+`"`,
w-lipgloss.Width(cmdEOL)-baseStyle.GetHorizontalFrameSize()*2,
)
leftPad := strings.Repeat(" ", baseStyle.GetMarginLeft())
cmdLines := strings.Split(cmd, "\n")
for i, l := range cmdLines {
b.WriteString(leftPad)
b.WriteString(l)
if i < len(cmdLines)-1 {
b.WriteString(cmdEOL)
b.WriteRune('\n')
}
}
b.WriteRune('\n')

fmt.Println(b.String())
} else {
fmt.Print(mnemonic)
}
Expand All @@ -57,17 +94,19 @@ Store them somewhere safe, print or memorize them.`))
mnemonic string
restoreCmd = &coral.Command{
Use: "restore",
Short: "Recreate a key using the given mnemonic words",
Example: ` melt restore --mnemonic \"list of words\" ./restored_id25519
melt restore ./restored_id25519 < mnemonic`,
Short: "Recreate a key using the given seed phrase",
Example: ` melt restore --seed "seed phrase" ./restored_id25519
melt restore ./restored_id25519 < seed`,
Aliases: []string{"res", "r"},
Args: coral.ExactArgs(1),
RunE: func(cmd *coral.Command, args []string) error {
if err := restore(maybeFile(mnemonic), args[0]); err != nil {
return err
}

fmt.Println(restoreStyle.Render(fmt.Sprintf(`Successfully restored keys to '%[1]s' and '%[1]s.pub'!`, args[0])))
pub := keyPathStyle.Render(args[0])
priv := keyPathStyle.Render(args[0] + ".pub")
fmt.Println(baseStyle.Render(fmt.Sprintf("\nSuccessfully restored keys to %s and %s", pub, priv)))
return nil
},
}
Expand All @@ -94,8 +133,8 @@ Store them somewhere safe, print or memorize them.`))
func init() {
rootCmd.AddCommand(restoreCmd, manCmd)

restoreCmd.PersistentFlags().StringVarP(&mnemonic, "mnemonic", "m", "-", "Mnemonic set of words given by the backup command")
_ = restoreCmd.MarkFlagRequired("mnemonic")
restoreCmd.PersistentFlags().StringVarP(&mnemonic, "seed", "s", "-", "Seed phrase")
_ = restoreCmd.MarkFlagRequired("seed")
}

func main() {
Expand Down Expand Up @@ -176,3 +215,26 @@ func restore(mnemonic, path string) error {
}
return nil
}

func getWidth(max int) int {
w, _, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil || w > max {
return maxWidth
}
return w
}

func renderBlock(w io.Writer, s lipgloss.Style, width int, str string) {
_, _ = io.WriteString(w, s.Copy().Width(width).Render(str))
_, _ = io.WriteString(w, "\n")
}

func completeColor(truecolor, ansi256, ansi string) string {
switch lipgloss.ColorProfile() {
case termenv.TrueColor:
return truecolor
case termenv.ANSI256:
return ansi256
}
return ansi
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ require (
github.com/muesli/coral v1.0.0
github.com/muesli/mango-coral v1.0.1
github.com/muesli/roff v0.1.0
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0
github.com/tyler-smith/go-bip39 v1.1.0
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
)

require (
Expand All @@ -22,7 +23,6 @@ require (
github.com/muesli/mango v0.1.0 // indirect
github.com/muesli/mango-pflag v0.1.0 // indirect
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 // indirect
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 // indirect
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE=
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down