package cmd import ( "bytes" "errors" "fmt" "io" "os" "os/exec" "path/filepath" "strings" "filippo.io/age" "filippo.io/age/agessh" "github.com/urfave/cli/v2" ) var ( version = "develop" debug = false ) func New() *cli.App { app := cli.NewApp() app.Name = "git-age" app.Version = version app.Usage = "Git encryption using age" app.Commands = []*cli.Command{ Clean, Identity, Init, Smudge, TextConv, } app.Flags = []cli.Flag{ &cli.BoolFlag{ Name: "debug", Aliases: []string{"d"}, Usage: "Debug mode", Destination: &debug, }, } return app } func parseIdentityFile(file string) ([]age.Identity, error) { fi, err := os.Open(file) if err != nil { return nil, err } defer fi.Close() // First try age i, err := age.ParseIdentities(fi) if err != nil { if debug { fmt.Fprintf(os.Stderr, "could not parse %q as age identities, trying ssh next: %v\n", file, err) } ageErr := err // Fall back to ssh fi.Seek(0, 0) content, err := io.ReadAll(fi) if err != nil { return nil, err } si, err := agessh.ParseIdentity(content) if err != nil { return nil, fmt.Errorf("could not parse identity file: age -> %w, ssh -> %w", ageErr, err) } i = []age.Identity{si} } return i, nil } func ageRecipients(file string) ([]age.Recipient, error) { cfg, err := LoadConfig() if err != nil { return nil, err } val, ok := cfg[file] if !ok { return nil, fmt.Errorf("no config found for %q", file) } return val.Recipients() } var ErrNoIdentities = errors.New("no identities found") func listIdentities() ([]string, error) { out, err := cmd("git", "config", "--get-all", "git-age.identity") if err != nil { if out == "" { return nil, ErrNoIdentities } return nil, err } return strings.Fields(out), nil } func ageIdentities() ([]age.Identity, error) { keyFiles, err := listIdentities() if err != nil { return nil, err } var identities []age.Identity for _, keyFile := range keyFiles { i, err := parseIdentityFile(keyFile) if err != nil { return identities, err } identities = append(identities, i...) } return identities, nil } func gitBaseDir() (string, error) { out, err := cmd("git", "rev-parse", "--show-toplevel") if err != nil { return "", err } stdout := strings.TrimSpace(string(out)) return stdout, nil } func gitConfigDir(file string) (string, error) { stdout, err := gitBaseDir() if err != nil { return "", err } normalFile := strings.ReplaceAll(file, string(filepath.Separator), "!") dir := filepath.Join(stdout, ".git", "git-age", normalFile) return dir, os.MkdirAll(dir, os.ModePerm) } func cmd(command string, args ...string) (string, error) { var stdout bytes.Buffer c := exec.Command(command, args...) c.Stdout = &stdout if debug { c.Stderr = os.Stderr } if err := c.Run(); err != nil { return "", err } return stdout.String(), nil }