mirror of https://git.jolheiser.com/git-age
186 lines
3.6 KiB
Go
186 lines
3.6 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"filippo.io/age"
|
|
"filippo.io/age/agessh"
|
|
"github.com/bmatcuk/doublestar/v4"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
var (
|
|
version = "develop"
|
|
debug = false
|
|
)
|
|
|
|
const REKEY = "GIT_AGE_REKEY"
|
|
|
|
func New() *cli.App {
|
|
app := cli.NewApp()
|
|
app.Name = "git-age"
|
|
app.Version = version
|
|
app.Usage = "Git encryption using age"
|
|
app.Commands = []*cli.Command{
|
|
Add,
|
|
Clean,
|
|
Identity,
|
|
Init,
|
|
Rekey,
|
|
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
|
|
}
|
|
for glob, val := range cfg {
|
|
match, err := doublestar.Match(glob, file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bad glob %q: %w", glob, err)
|
|
}
|
|
if match {
|
|
return val.Recipients()
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("no config found for %q", file)
|
|
}
|
|
|
|
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 gitRelPath(file string) (string, error) {
|
|
base, err := gitBaseDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
apn, err := filepath.Abs(file)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strings.TrimPrefix(apn, base+"/"), 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 hasAttr(file string) (bool, error) {
|
|
out, err := cmd("git", "check-attr", "-a", file)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for _, line := range strings.Split(out, "\n") {
|
|
if line != "" && strings.Fields(line)[2] == "git-age" {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
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
|
|
}
|