Compare commits
1
Commits
v0.2.1
...
f9a7ed73be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9a7ed73be
|
No files matched your search
@@ -69,11 +69,7 @@ func installHandler(cfg config.Config, configPath, term string) (string, string,
|
||||
if _, err := launchCommand(cfg, configPath, term, exe, "lazysecrets://check"); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
launch := []string{exe}
|
||||
if configPath != defaultConfigPath() {
|
||||
launch = append(launch, "--config", configPath)
|
||||
}
|
||||
launch = append(launch, "launch")
|
||||
launch := append(append([]string{exe}, configArgs(configPath)...), "launch")
|
||||
if term != "" {
|
||||
launch = append(launch, "--terminal", term)
|
||||
}
|
||||
@@ -188,8 +184,14 @@ func launchCommand(cfg config.Config, configPath, term, exe, link string) ([]str
|
||||
// innerCommand runs lazysecrets on link, with the environment it needs.
|
||||
func innerCommand(configPath, exe, link string) []string {
|
||||
inner := append(append([]string{"/usr/bin/env"}, linkhandler.Env(os.Getenv)...), exe)
|
||||
if configPath != defaultConfigPath() {
|
||||
inner = append(inner, "--config", configPath)
|
||||
}
|
||||
inner = append(inner, configArgs(configPath)...)
|
||||
return append(inner, "open", link)
|
||||
}
|
||||
|
||||
// configArgs passes the config files on, unless they're the default.
|
||||
func configArgs(configPath string) []string {
|
||||
if configPath == defaultConfigPath() {
|
||||
return nil
|
||||
}
|
||||
return []string{"--config", configPath}
|
||||
}
|
||||
+47
-64
@@ -171,26 +171,22 @@ Flags:
|
||||
return nil
|
||||
}
|
||||
|
||||
var open, getRef *ref.Ref
|
||||
sub := "" // the subcommand; "" browses
|
||||
var subArgs []string
|
||||
var open *ref.Ref
|
||||
var getRef ref.Ref
|
||||
var openAction string
|
||||
var shareArgs, listArgs []string
|
||||
printKeys := false
|
||||
switch args := flag.Args(); {
|
||||
case len(args) > 0 && args[0] == "share":
|
||||
shareArgs = args[1:]
|
||||
if len(shareArgs) == 0 {
|
||||
shareArgs = []string{} // still sharing: says how
|
||||
}
|
||||
case len(args) > 0 && args[0] == "list":
|
||||
listArgs = append([]string{}, args[1:]...) // not nil: listing
|
||||
case len(args) > 0 && (args[0] == "share" || args[0] == "list"):
|
||||
sub, subArgs = args[0], args[1:]
|
||||
case len(args) == 1 && args[0] == "keybindings":
|
||||
printKeys = true
|
||||
sub = "keybindings"
|
||||
case len(args) > 0 && args[0] == "get":
|
||||
r, _, err := refArg("get", args[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
getRef = &r
|
||||
sub, getRef = "get", r
|
||||
case len(args) > 0 && args[0] == "handler":
|
||||
return handler(*configPath, args[1:])
|
||||
case len(args) > 0 && args[0] == "launch":
|
||||
@@ -236,7 +232,7 @@ Flags:
|
||||
if err := checkCommandKeys(&keymap, cfg.CustomCommands); err != nil {
|
||||
return err
|
||||
}
|
||||
if printKeys {
|
||||
if sub == "keybindings" {
|
||||
fmt.Print(keymap.YAML())
|
||||
return nil
|
||||
}
|
||||
@@ -264,25 +260,13 @@ Flags:
|
||||
}
|
||||
// timeoutFor is how long a request to a provider of kind k may take.
|
||||
timeoutFor := func(k provider.Kind) time.Duration { return cmp.Or(timeouts[k], *timeout) }
|
||||
if !set["clipboard-clear"] && cfg.Values.ClipboardClear != nil {
|
||||
*clipClear = *cfg.Values.ClipboardClear
|
||||
}
|
||||
expiryDays := 30
|
||||
if cfg.UI.ExpiryWarningDays != nil {
|
||||
expiryDays = *cfg.UI.ExpiryWarningDays
|
||||
}
|
||||
revealTimeout := 60 * time.Second
|
||||
if cfg.Values.RevealTimeout != nil {
|
||||
revealTimeout = *cfg.Values.RevealTimeout
|
||||
}
|
||||
loadDelay := 150 * time.Millisecond
|
||||
if cfg.UI.LoadDelay != nil {
|
||||
loadDelay = *cfg.UI.LoadDelay
|
||||
}
|
||||
var updateEvery time.Duration // 0: the app's default, a day
|
||||
if cfg.Updates.AutoCheckInterval != nil {
|
||||
updateEvery = *cfg.Updates.AutoCheckInterval
|
||||
if !set["clipboard-clear"] {
|
||||
*clipClear = valueOr(cfg.Values.ClipboardClear, *clipClear)
|
||||
}
|
||||
expiryWarning := time.Duration(valueOr(cfg.UI.ExpiryWarningDays, 30)) * 24 * time.Hour
|
||||
revealTimeout := valueOr(cfg.Values.RevealTimeout, 60*time.Second)
|
||||
loadDelay := valueOr(cfg.UI.LoadDelay, 150*time.Millisecond)
|
||||
updateEvery := valueOr(cfg.Updates.AutoCheckInterval, 0) // 0: the app's default, a day
|
||||
if !set["icons"] {
|
||||
switch {
|
||||
case os.Getenv("LAZYSECRETS_ICONS") != "":
|
||||
@@ -292,7 +276,17 @@ Flags:
|
||||
}
|
||||
}
|
||||
|
||||
recentIcon := cfg.UI.RecentIcon != nil && *cfg.UI.RecentIcon
|
||||
recentIcon := valueOr(cfg.UI.RecentIcon, false)
|
||||
|
||||
startDir := *dir
|
||||
if startDir == "" {
|
||||
startDir, _ = os.Getwd()
|
||||
}
|
||||
// What was loaded last time.
|
||||
cacheFile := ""
|
||||
if d, err := config.CacheDir(); err == nil && !*demo && config.On(cfg.Storage.Cache) {
|
||||
cacheFile = filepath.Join(d, "cache.json")
|
||||
}
|
||||
|
||||
// Stars, notes and the layout. Demo mode keeps them in memory, so
|
||||
// sample vaults never end up in the real file.
|
||||
@@ -350,17 +344,9 @@ Flags:
|
||||
providers = append(providers, dbx)
|
||||
}
|
||||
if config.On(pc.Repos.Enabled) {
|
||||
start := *dir
|
||||
if start == "" {
|
||||
start, _ = os.Getwd()
|
||||
}
|
||||
roots := repo.DefaultRoots
|
||||
if pc.Repos.Roots != nil {
|
||||
roots = *pc.Repos.Roots
|
||||
}
|
||||
repos := repo.New(repo.Settings{
|
||||
Dir: start, Paths: pc.Repos.Paths, Roots: roots, Starred: starredRepos(st),
|
||||
Include: pc.Repos.Include, Exclude: pc.Repos.Exclude,
|
||||
Dir: startDir, Paths: pc.Repos.Paths, Roots: valueOr(pc.Repos.Roots, repo.DefaultRoots),
|
||||
Starred: starredRepos(st), Include: pc.Repos.Include, Exclude: pc.Repos.Exclude,
|
||||
})
|
||||
// The repo it started in (shown on top as Repo) and the
|
||||
// others, checked for secret files in the background like
|
||||
@@ -376,19 +362,14 @@ Flags:
|
||||
// the standard logger, which would draw over the TUI.
|
||||
log.SetOutput(io.Discard)
|
||||
|
||||
if getRef != nil {
|
||||
switch sub {
|
||||
case "get":
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeoutFor(getRef.Kind))
|
||||
defer cancel()
|
||||
return get(ctx, providers, st, *getRef)
|
||||
}
|
||||
if listArgs != nil {
|
||||
cachePath := ""
|
||||
if d, err := config.CacheDir(); err == nil && !*demo && config.On(cfg.Storage.Cache) {
|
||||
cachePath = filepath.Join(d, "cache.json")
|
||||
}
|
||||
return listCmd(providers, st, cachePath, timeoutFor, theme, *icons, recentIcon, time.Duration(expiryDays)*24*time.Hour, listArgs, os.Stdout)
|
||||
}
|
||||
if shareArgs != nil {
|
||||
return get(ctx, providers, st, getRef)
|
||||
case "list":
|
||||
return listCmd(providers, st, cacheFile, timeoutFor, theme, *icons, recentIcon, expiryWarning, subArgs, os.Stdout)
|
||||
case "share":
|
||||
// The files from a repo, the key from whichever provider it's
|
||||
// in: the longest timeout.
|
||||
d := *timeout
|
||||
@@ -397,7 +378,7 @@ Flags:
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), d)
|
||||
defer cancel()
|
||||
return shareFiles(ctx, providers, st, cfg.Sharing.Key, shareRules(cfg), shareArgs, os.Stdout)
|
||||
return shareFiles(ctx, providers, st, cfg.Sharing.Key, shareRules(cfg), subArgs, os.Stdout)
|
||||
}
|
||||
|
||||
// Links opened elsewhere (lazysecrets launch) can come here.
|
||||
@@ -431,16 +412,10 @@ Flags:
|
||||
onActive = srv.Touch
|
||||
}
|
||||
|
||||
// What was loaded last time, and where it was left in this folder.
|
||||
var cacheFile, placeDir string
|
||||
// Where it was left in this folder.
|
||||
placeDir := ""
|
||||
if !*demo {
|
||||
if d, err := config.CacheDir(); err == nil && config.On(cfg.Storage.Cache) {
|
||||
cacheFile = filepath.Join(d, "cache.json")
|
||||
}
|
||||
placeDir = *dir
|
||||
if placeDir == "" {
|
||||
placeDir, _ = os.Getwd()
|
||||
}
|
||||
placeDir = startDir
|
||||
if abs, err := filepath.Abs(placeDir); err == nil {
|
||||
placeDir = abs
|
||||
}
|
||||
@@ -457,7 +432,7 @@ Flags:
|
||||
NoHistory: !config.On(cfg.Storage.History),
|
||||
OnActive: onActive,
|
||||
RevealTimeout: revealTimeout,
|
||||
ExpiryWarning: time.Duration(expiryDays) * 24 * time.Hour,
|
||||
ExpiryWarning: expiryWarning,
|
||||
HideOnBlur: config.On(cfg.Values.HideOnBlur),
|
||||
HandlerSetup: setup,
|
||||
Updater: updater(cfg.Updates.Sources),
|
||||
@@ -490,6 +465,14 @@ Flags:
|
||||
return err
|
||||
}
|
||||
|
||||
// valueOr is *p, or def when p is nil (not set in the config).
|
||||
func valueOr[T any](p *T, def T) T {
|
||||
if p == nil {
|
||||
return def
|
||||
}
|
||||
return *p
|
||||
}
|
||||
|
||||
// configFiles are the config files given, for the status pane: those
|
||||
// that exist, or the first if none does (where to create one).
|
||||
func configFiles(list string) []string {
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -32,8 +33,12 @@ func updater(servers []string) *app.Updater {
|
||||
// updateCmd is lazysecrets update [--check]: the newer release's
|
||||
// changelog, then (unless --check) install it.
|
||||
func updateCmd(configPath string, args []string) error {
|
||||
check := len(args) == 1 && args[0] == "--check"
|
||||
if len(args) > 0 && !check {
|
||||
fs := flag.NewFlagSet("update", flag.ContinueOnError)
|
||||
check := fs.Bool("check", false, "just say if there's a newer release")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if fs.NArg() != 0 {
|
||||
return errors.New("usage: lazysecrets update [--check]")
|
||||
}
|
||||
cfg, err := config.LoadAll(strings.Split(configPath, ","))
|
||||
@@ -49,14 +54,14 @@ func updateCmd(configPath string, args []string) error {
|
||||
return fmt.Errorf("looking for a newer lazysecrets: %w", err)
|
||||
}
|
||||
if !update.Newer(current, r.Tag) {
|
||||
if !update.Newer(current, "v999.0.0") {
|
||||
if strings.HasPrefix(current, "dev") {
|
||||
return fmt.Errorf("this is a development build (%s); the latest release is %s: install it with install.sh, or go install …@%s", current, r.Tag, r.Tag)
|
||||
}
|
||||
fmt.Printf("lazysecrets %s is the latest (%s)\n", current, r.Tag)
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("lazysecrets %s is out (you have %s):\n\n%s\n\n", r.Tag, current, indent(r.Notes(current)))
|
||||
if check {
|
||||
if *check {
|
||||
fmt.Println("lazysecrets update installs it.")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// version is set by release builds:
|
||||
@@ -31,7 +30,7 @@ func versionString() string {
|
||||
// go install …@<commit>.
|
||||
if m := pseudo.FindStringSubmatch(v); m != nil {
|
||||
dirty := ""
|
||||
if strings.HasSuffix(v, "+dirty") {
|
||||
if m[2] != "" {
|
||||
dirty = "-dirty"
|
||||
}
|
||||
return "dev-" + m[1][:7] + dirty
|
||||
|
||||
@@ -35,15 +35,18 @@ func Dir(stateDir string) string {
|
||||
type Status struct {
|
||||
PID int `json:"pid"`
|
||||
LastActive time.Time `json:"lastActive"` // last key press or focus
|
||||
// Reach is how readily it can be shown: 2 in view (outside tmux, or
|
||||
// in a tmux session a client shows), 1 in a tmux session that a tmux
|
||||
// client can be switched to, 0 not at all.
|
||||
Reach int `json:"reach"`
|
||||
Reach int `json:"reach"` // how readily it can be shown: unreachable, switchable or inView
|
||||
// App is the terminal app it would show in (macOS), preferring the
|
||||
// app the link was clicked in.
|
||||
App string `json:"app,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
unreachable = 0
|
||||
switchable = 1 // in a tmux session that a tmux client can be switched to
|
||||
inView = 2 // outside tmux, or in a tmux session a client shows
|
||||
)
|
||||
|
||||
type request struct {
|
||||
Op string `json:"op"` // "status" or "open"
|
||||
Link string `json:"link,omitempty"`
|
||||
@@ -187,7 +190,7 @@ func Best(found []Found, front string, terminal bool) (Found, bool) {
|
||||
best = f
|
||||
}
|
||||
}
|
||||
return best, best.Reach > 0
|
||||
return best, best.Reach > unreachable
|
||||
}
|
||||
|
||||
// Open hands link, clicked in front, to f.
|
||||
|
||||
+22
-23
@@ -1,6 +1,7 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"os"
|
||||
"os/exec"
|
||||
"slices"
|
||||
@@ -25,7 +26,8 @@ func tmux(args ...string) (string, error) {
|
||||
func inTmux() bool { return os.Getenv("TMUX") != "" && os.Getenv("TMUX_PANE") != "" }
|
||||
|
||||
type client struct {
|
||||
name, session, activity string // activity: Unix seconds
|
||||
name, session string
|
||||
activity int // Unix seconds
|
||||
pid int
|
||||
app string
|
||||
}
|
||||
@@ -42,30 +44,31 @@ func clients() []client {
|
||||
if len(f) < 4 {
|
||||
continue
|
||||
}
|
||||
pid, err := strconv.Atoi(f[1])
|
||||
if err != nil {
|
||||
activity, err1 := strconv.Atoi(f[0])
|
||||
pid, err2 := strconv.Atoi(f[1])
|
||||
if err1 != nil || err2 != nil {
|
||||
continue
|
||||
}
|
||||
cs = append(cs, client{name: f[2], session: f[3], activity: f[0], pid: pid, app: linkhandler.HostApp(pid)})
|
||||
cs = append(cs, client{name: f[2], session: f[3], activity: activity, pid: pid, app: linkhandler.HostApp(pid)})
|
||||
}
|
||||
// Activity has the same width for centuries: strings compare like
|
||||
// numbers.
|
||||
slices.SortStableFunc(cs, func(a, b client) int { return strings.Compare(b.activity, a.activity) })
|
||||
slices.SortStableFunc(cs, func(a, b client) int { return cmp.Compare(b.activity, a.activity) })
|
||||
return cs
|
||||
}
|
||||
|
||||
// place is where this instance would show: the tmux client (zero outside
|
||||
// tmux), the app, and reach: 2 already on screen there (outside tmux, or
|
||||
// a client on its session), 1 after switching a client, 0 nowhere.
|
||||
// tmux), the app, and its reach.
|
||||
func place(front string) (client, string, int) {
|
||||
if !inTmux() {
|
||||
return client{}, linkhandler.HostApp(os.Getpid()), 2
|
||||
return client{}, linkhandler.HostApp(os.Getpid()), inView
|
||||
}
|
||||
session, err := tmux("display-message", "-p", "-t", os.Getenv("TMUX_PANE"), "#{session_name}")
|
||||
if err != nil {
|
||||
return client{}, "", 0
|
||||
return client{}, "", unreachable
|
||||
}
|
||||
cs := clients()
|
||||
if len(cs) == 0 {
|
||||
return client{}, "", unreachable
|
||||
}
|
||||
// Best first: in the front app on its session, in the front app,
|
||||
// on its session, any.
|
||||
rank := func(c client) int {
|
||||
@@ -78,20 +81,16 @@ func place(front string) (client, string, int) {
|
||||
}
|
||||
return r
|
||||
}
|
||||
var best client
|
||||
found := false
|
||||
for _, c := range cs {
|
||||
if !found || rank(c) > rank(best) {
|
||||
best, found = c, true
|
||||
best := cs[0]
|
||||
for _, c := range cs[1:] {
|
||||
if rank(c) > rank(best) {
|
||||
best = c
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return client{}, "", 0
|
||||
}
|
||||
if best.session == session {
|
||||
return best, best.app, 2
|
||||
return best, best.app, inView
|
||||
}
|
||||
return best, best.app, 1
|
||||
return best, best.app, switchable
|
||||
}
|
||||
|
||||
// Raise brings this instance into view for a link clicked in front: in
|
||||
@@ -99,14 +98,14 @@ func place(front string) (client, string, int) {
|
||||
// then its app to the front, showing a hidden dropdown terminal.
|
||||
func Raise(front string) {
|
||||
c, app, reach := place(front)
|
||||
if reach == 0 {
|
||||
if reach == unreachable {
|
||||
return
|
||||
}
|
||||
if inTmux() {
|
||||
pane := os.Getenv("TMUX_PANE")
|
||||
_, _ = tmux("select-window", "-t", pane)
|
||||
_, _ = tmux("select-pane", "-t", pane)
|
||||
if reach == 1 {
|
||||
if reach == switchable {
|
||||
_, _ = tmux("switch-client", "-c", c.name, "-t", pane)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ const (
|
||||
// DefaultServers are where releases are looked for, in order.
|
||||
var DefaultServers = []string{GitHubRepo, DefaultRepo}
|
||||
|
||||
// latestTimeout bounds asking one server for the latest release, so a
|
||||
// server that's down leaves time for the next.
|
||||
const latestTimeout = 5 * time.Second
|
||||
// serverTimeout bounds asking one server for the latest release or its
|
||||
// checksums, so a server that's down leaves time for the next.
|
||||
const serverTimeout = 5 * time.Second
|
||||
|
||||
// maxDownload bounds what's read from the server.
|
||||
const maxDownload = 200 << 20
|
||||
@@ -151,7 +151,7 @@ func (c *Client) latest(ctx context.Context, repo string) (Release, error) {
|
||||
if err != nil {
|
||||
return Release{}, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, latestTimeout)
|
||||
ctx, cancel := context.WithTimeout(ctx, serverTimeout)
|
||||
defer cancel()
|
||||
// The recent releases in one request (per_page on GitHub, limit on
|
||||
// Gitea), for their changelogs.
|
||||
@@ -220,7 +220,7 @@ func (c *Client) Install(ctx context.Context, r Release, exe string) error {
|
||||
if m := Managed(exe); m != "" {
|
||||
return fmt.Errorf("%s is managed by %s: update it there", exe, m)
|
||||
}
|
||||
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" || runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
|
||||
if (runtime.GOOS != "darwin" && runtime.GOOS != "linux") || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {
|
||||
return fmt.Errorf("no release build for %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
version := strings.TrimPrefix(r.Tag, "v")
|
||||
@@ -270,7 +270,7 @@ func (c *Client) download(ctx context.Context, tag, file string) ([]byte, error)
|
||||
var errs []error
|
||||
for _, repo := range c.servers() {
|
||||
base := strings.TrimSuffix(repo, "/") + "/releases/download/" + tag + "/"
|
||||
sctx, cancel := context.WithTimeout(ctx, latestTimeout)
|
||||
sctx, cancel := context.WithTimeout(ctx, serverTimeout)
|
||||
sums, err := c.get(sctx, base+"checksums.txt", 1<<20)
|
||||
cancel()
|
||||
if err != nil {
|
||||
|
||||
Reference in new issue
Block a user