feat: share env files as encrypted links
S → f on a repo, an env file or a key in one copies a lazysecrets://share link holding the env files, encrypted (age) with a secret's value: whoever can read that secret can open it. The key is share.key in the repo's .lazysecrets.yaml, else in the config, else picked when sharing; on Azure the link pins its version. Opening one says what it holds, decrypts when asked, and saves the files in the clone of the repo (or a folder chosen), replacing existing ones only when asked. lazysecrets share prints a link from the command line. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
26 files changed
+1541
-98
No files matched your search
@@ -311,8 +311,8 @@ func (p *Provider) Containers(ctx context.Context, src provider.Source) ([]provi
|
||||
c.Detail = "sops · " + strings.Join(meta.methods, ", ")
|
||||
case isSops:
|
||||
return // a SOPS file where .sops.yaml says SOPS files don't go
|
||||
case isEnv(f):
|
||||
c.Detail = "env · plain text"
|
||||
case IsEnv(f):
|
||||
c.Detail = provider.DetailPlainEnv
|
||||
default:
|
||||
return
|
||||
}
|
||||
@@ -464,6 +464,16 @@ func (p *Provider) Locate(_ context.Context, host string) (string, bool, error)
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
// Root is the root of the git repo dir is in, and the repo's ID: its
|
||||
// remote without scheme or .git (a repo link's host), else the root.
|
||||
func Root(dir string) (root, id string, err error) {
|
||||
root, err = gitRoot(dir)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("%s isn't in a git repository", dir)
|
||||
}
|
||||
return root, info(root, "").id, nil
|
||||
}
|
||||
|
||||
// --- git ---
|
||||
|
||||
func gitRoot(dir string) (string, error) {
|
||||
@@ -568,6 +578,27 @@ func normalizeRemote(remote string) (string, string) {
|
||||
type lazysecretsFile struct {
|
||||
Include []string `yaml:"include"`
|
||||
Exclude []string `yaml:"exclude"`
|
||||
Share struct {
|
||||
// Key is the link to the secret that encrypts the repo's
|
||||
// shared env files (package share).
|
||||
Key string `yaml:"key"`
|
||||
} `yaml:"share"`
|
||||
}
|
||||
|
||||
// ShareKey is the share.key of the repo's .lazysecrets.yaml, if any.
|
||||
func ShareKey(root string) (string, error) {
|
||||
b, err := os.ReadFile(filepath.Join(root, ".lazysecrets.yaml"))
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return "", nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var f lazysecretsFile
|
||||
if err := yaml.Unmarshal(b, &f); err != nil {
|
||||
return "", fmt.Errorf("%s/.lazysecrets.yaml: %w", root, err)
|
||||
}
|
||||
return f.Share.Key, nil
|
||||
}
|
||||
|
||||
// selector says which files to look at: user (the include/exclude of the
|
||||
@@ -618,7 +649,7 @@ func envFiles(root string, tracked []string) []string {
|
||||
dirs := map[string]bool{".": true}
|
||||
for _, f := range tracked {
|
||||
isTracked[f] = true
|
||||
if isEnv(f) {
|
||||
if IsEnv(f) {
|
||||
out = append(out, f)
|
||||
}
|
||||
if d := path.Dir(f); !dirs[d] && !inPackages(f) {
|
||||
@@ -632,7 +663,7 @@ func envFiles(root string, tracked []string) []string {
|
||||
}
|
||||
for _, e := range entries {
|
||||
f := path.Join(d, e.Name())
|
||||
if e.Type().IsRegular() && !isTracked[f] && isEnv(f) {
|
||||
if e.Type().IsRegular() && !isTracked[f] && IsEnv(f) {
|
||||
out = append(out, f)
|
||||
}
|
||||
}
|
||||
@@ -663,10 +694,10 @@ func inPackages(f string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// isEnv reports whether a file is named like an env file with real values:
|
||||
// IsEnv reports whether a file is named like an env file with real values:
|
||||
// .env, .env.local, prod.env; not .env.example and the like, nor one in
|
||||
// dependencies.
|
||||
func isEnv(f string) bool {
|
||||
func IsEnv(f string) bool {
|
||||
if inPackages(f) {
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user