Files
x/internal/cli/generate.go
T
sneak 5932cc12ee Remove cbk and mnemonic commands from sutil
sutil holds only the utilities wanted on every machine; it is not a
command-line front end for the packages in pkg/. cbk and bip39wordlist
remain libraries with no CLI surface. The README states the boundary.
2026-10-31 10:16:10 -07:00

126 lines
3.2 KiB
Go

package cli
import (
"fmt"
"io"
"github.com/spf13/cobra"
"sneak.berlin/go/x/internal/pwgen"
"sneak.berlin/go/x/internal/randstream"
)
// streamChunkSize is the write size used by `generate random`. Large
// writes keep the per-syscall overhead negligible.
const streamChunkSize = 1 << 20
// newGenerateCommand returns `sutil generate`, the parent of everything
// that produces random output.
func newGenerateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "generate",
Aliases: []string{"gen"},
Short: "Generate random bytes and passwords",
}
cmd.AddCommand(
newGenerateRandomCommand(),
newGeneratePasswordCommand(),
)
return cmd
}
// newGenerateRandomCommand returns `sutil generate random [seedstr]`.
func newGenerateRandomCommand() *cobra.Command {
var byteCount int64
cmd := &cobra.Command{
Use: "random [seedstr]",
Short: "Write a fast stream of pseudorandom bytes to stdout",
Long: `Writes pseudorandom bytes to stdout until stopped, or until --count
bytes have been written.
With seedstr the stream is deterministic: the same seed always produces
the same bytes. Without it the stream is keyed from the operating
system's secure random source and is different every run.
This exists because /dev/urandom is slow.`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
seed := ""
if len(args) == 1 {
seed = args[0]
}
stream, err := randstream.New(randstream.Params{Seed: seed})
if err != nil {
return err
}
return streamBytes(cmd.OutOrStdout(), stream, byteCount)
},
}
cmd.Flags().Int64VarP(
&byteCount, "count", "c", 0, "stop after this many bytes (0 = never)",
)
return cmd
}
// newGeneratePasswordCommand returns `sutil generate password`.
func newGeneratePasswordCommand() *cobra.Command {
return &cobra.Command{
Use: "password",
Aliases: []string{"pw"},
Short: "Print a memorable password built from system dictionary words",
Long: `Prints a password of the form
<word><digit><delim><word><digit><delim><word><digit>
for example marble7-sprout2-velvet9. The words come from the system word
list (/usr/share/dict/words), the delimiter is chosen at random from
` + pwgen.Delimiters + `
and is the same in both positions. The command panics if the system has
no usable word list.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
generator, err := pwgen.New(pwgen.Params{})
if err != nil {
panic("sutil: cannot load the system word list: " + err.Error())
}
password, err := generator.Generate()
if err != nil {
return err
}
_, err = fmt.Fprintln(cmd.OutOrStdout(), password)
return err
},
}
}
// streamBytes copies limit bytes (or everything, when limit is zero) from
// src to dst in streamChunkSize chunks.
func streamBytes(dst io.Writer, src io.Reader, limit int64) error {
if limit > 0 {
src = io.LimitReader(src, limit)
}
// Wrapping dst hides any ReadFrom method it has, so io.CopyBuffer
// cannot bypass the large buffer.
_, err := io.CopyBuffer(writerOnly{dst}, src, make([]byte, streamChunkSize))
return err
}
// writerOnly exposes only the Write method of the wrapped writer.
type writerOnly struct {
io.Writer
}