feat: config grouped by area, with per-provider timeouts and an update interval #60
No files matched your search
@@ -102,7 +102,7 @@ names and metadata of what you've loaded in a cache file readable only by
|
|||||||
you, to show it at once next time (`cache: false` turns it off). It talks
|
you, to show it at once next time (`cache: false` turns it off). It talks
|
||||||
only to the services you use it with, and collects no usage data. The one
|
only to the services you use it with, and collects no usage data. The one
|
||||||
call of its own is a daily check for a new release, on GitHub or else
|
call of its own is a daily check for a new release, on GitHub or else
|
||||||
git.kebcloud.no, which `updateCheck: false` turns off.
|
git.kebcloud.no, which `updates.autoCheck: false` turns off.
|
||||||
|
|
||||||
## Repositories
|
## Repositories
|
||||||
|
|
||||||
|
|||||||
@@ -225,6 +225,10 @@ Flags:
|
|||||||
if cfg.LoadDelay != nil {
|
if cfg.LoadDelay != nil {
|
||||||
loadDelay = *cfg.LoadDelay
|
loadDelay = *cfg.LoadDelay
|
||||||
}
|
}
|
||||||
|
var updateEvery time.Duration // 0: the app's default, a day
|
||||||
|
if cfg.Updates.AutoCheckInterval != nil {
|
||||||
|
updateEvery = *cfg.Updates.AutoCheckInterval
|
||||||
|
}
|
||||||
if !set["icons"] {
|
if !set["icons"] {
|
||||||
switch {
|
switch {
|
||||||
case os.Getenv("LAZYSECRETS_ICONS") != "":
|
case os.Getenv("LAZYSECRETS_ICONS") != "":
|
||||||
@@ -379,8 +383,9 @@ Flags:
|
|||||||
ExpiryWarning: time.Duration(expiryDays) * 24 * time.Hour,
|
ExpiryWarning: time.Duration(expiryDays) * 24 * time.Hour,
|
||||||
HideOnBlur: config.On(cfg.HideOnBlur),
|
HideOnBlur: config.On(cfg.HideOnBlur),
|
||||||
HandlerSetup: setup,
|
HandlerSetup: setup,
|
||||||
Updater: updater(cfg.Releases),
|
Updater: updater(cfg.Updates.UpdateSources),
|
||||||
UpdateCheck: config.On(cfg.UpdateCheck),
|
UpdateCheck: config.On(cfg.Updates.AutoCheck),
|
||||||
|
UpdateEvery: updateEvery,
|
||||||
Keys: &keymap,
|
Keys: &keymap,
|
||||||
Commands: cfg.CustomCommands,
|
Commands: cfg.CustomCommands,
|
||||||
Version: versionString(),
|
Version: versionString(),
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func updateCmd(configPath string, args []string) error {
|
|||||||
current := versionString()
|
current := versionString()
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
c := &update.Client{Servers: cfg.Releases}
|
c := &update.Client{Servers: cfg.Updates.UpdateSources}
|
||||||
r, err := c.Latest(ctx)
|
r, err := c.Latest(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("looking for a newer lazysecrets: %w", err)
|
return fmt.Errorf("looking for a newer lazysecrets: %w", err)
|
||||||
|
|||||||
+10
-7
@@ -16,8 +16,10 @@ timeout: 30s # per backend request
|
|||||||
loadDelay: 150ms # how long the cursor rests on a row before it loads; 0s loads at once
|
loadDelay: 150ms # how long the cursor rests on a row before it loads; 0s loads at once
|
||||||
sourcesView: grouped # or flat; ` toggles it
|
sourcesView: grouped # or flat; ` toggles it
|
||||||
collapseGroups: true # start with the Sources pane's groups folded; enter or space unfolds one
|
collapseGroups: true # start with the Sources pane's groups folded; enter or space unfolds one
|
||||||
updateCheck: true # look for a newer release once a day (U and lazysecrets update work either way)
|
updates:
|
||||||
releases: # where updates come from, tried in order (GitHub, or a Gitea or Forgejo server)
|
autoCheck: true # look for a newer release at startup (U and lazysecrets update work either way)
|
||||||
|
autoCheckInterval: 24h # at most this often
|
||||||
|
updateSources: # where updates come from, tried in order (GitHub, or a Gitea or Forgejo server)
|
||||||
- https://github.com/karleinarb/lazysecrets
|
- https://github.com/karleinarb/lazysecrets
|
||||||
- https://git.kebcloud.no/keb/lazysecrets
|
- https://git.kebcloud.no/keb/lazysecrets
|
||||||
history: true # count uses of items for Recent (R)
|
history: true # count uses of items for Recent (R)
|
||||||
@@ -136,11 +138,12 @@ are read from disk anyway. Deleting the file is always safe, and
|
|||||||
|
|
||||||
## Updates
|
## Updates
|
||||||
|
|
||||||
Once a day (`updateCheck`), in the background, lazysecrets asks for the
|
At startup, in the background, lazysecrets asks for the latest release
|
||||||
latest release; within the day it reuses the answer from the state file.
|
(`updates.autoCheck`), once a day or every `updates.autoCheckInterval`;
|
||||||
Releases are published on GitHub and on git.kebcloud.no, and it asks
|
until then it reuses the answer from the state file. Releases are
|
||||||
them in that order, the next when one doesn't answer within 5 seconds;
|
published on GitHub and on git.kebcloud.no, and it asks them in that
|
||||||
`releases` lists others, such as a mirror of your own. Offline, or with
|
order, the next when one doesn't answer within 5 seconds;
|
||||||
|
`updates.updateSources` lists others, such as a mirror of your own. Offline, or with
|
||||||
every server down, it says nothing. A newer release shows in the footer, `v0.1.1 ↑ v0.2.0`; `U` (or
|
every server down, it says nothing. A newer release shows in the footer, `v0.1.1 ↑ v0.2.0`; `U` (or
|
||||||
`lazysecrets update --check`) shows its changelog.
|
`lazysecrets update --check`) shows its changelog.
|
||||||
|
|
||||||
|
|||||||
+20
-10
@@ -38,14 +38,8 @@ type Config struct {
|
|||||||
SourcesView string `yaml:"sourcesView"`
|
SourcesView string `yaml:"sourcesView"`
|
||||||
// History counts uses of items for the Recent list; defaults to true.
|
// History counts uses of items for the Recent list; defaults to true.
|
||||||
History *bool `yaml:"history"`
|
History *bool `yaml:"history"`
|
||||||
// UpdateCheck looks for a newer release once a day, in the
|
// Updates sets how lazysecrets looks for newer releases.
|
||||||
// background; defaults to true. U and lazysecrets update work either
|
Updates Updates `yaml:"updates"`
|
||||||
// way.
|
|
||||||
UpdateCheck *bool `yaml:"updateCheck"`
|
|
||||||
// Releases are the repositories updates come from, tried in order:
|
|
||||||
// on GitHub, or a Gitea or Forgejo server. Defaults to GitHub, then
|
|
||||||
// git.kebcloud.no (update.DefaultServers).
|
|
||||||
Releases []string `yaml:"releases"`
|
|
||||||
// Cache keeps what was loaded (never values) between runs, so lists
|
// Cache keeps what was loaded (never values) between runs, so lists
|
||||||
// show at once and refresh in the background; defaults to true.
|
// show at once and refresh in the background; defaults to true.
|
||||||
Cache *bool `yaml:"cache"`
|
Cache *bool `yaml:"cache"`
|
||||||
@@ -68,6 +62,19 @@ type Config struct {
|
|||||||
Theme map[string][]string `yaml:"theme"`
|
Theme map[string][]string `yaml:"theme"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Updates struct {
|
||||||
|
// AutoCheck looks for a newer release in the background at startup,
|
||||||
|
// at most once per AutoCheckInterval; defaults to true. U and
|
||||||
|
// lazysecrets update work either way.
|
||||||
|
AutoCheck *bool `yaml:"autoCheck"`
|
||||||
|
// AutoCheckInterval defaults to 24h.
|
||||||
|
AutoCheckInterval *time.Duration `yaml:"autoCheckInterval"`
|
||||||
|
// UpdateSources are the repositories updates come from, tried in
|
||||||
|
// order: on GitHub, or a Gitea or Forgejo server. Defaults to GitHub,
|
||||||
|
// then git.kebcloud.no (update.DefaultServers).
|
||||||
|
UpdateSources []string `yaml:"updateSources"`
|
||||||
|
}
|
||||||
|
|
||||||
type LinkHandler struct {
|
type LinkHandler struct {
|
||||||
// Terminal to open links in, overriding the one handler install
|
// Terminal to open links in, overriding the one handler install
|
||||||
// detected: kitty, ghostty, wezterm, iterm2, terminal, alacritty,
|
// detected: kitty, ghostty, wezterm, iterm2, terminal, alacritty,
|
||||||
@@ -236,9 +243,12 @@ func (c Config) validate(path string) error {
|
|||||||
if d := c.LoadDelay; d != nil && (*d < 0 || *d > 5*time.Second) {
|
if d := c.LoadDelay; d != nil && (*d < 0 || *d > 5*time.Second) {
|
||||||
return fmt.Errorf("%s: loadDelay: %s is not between 0s and 5s", path, *d)
|
return fmt.Errorf("%s: loadDelay: %s is not between 0s and 5s", path, *d)
|
||||||
}
|
}
|
||||||
for i, r := range c.Releases {
|
if d := c.Updates.AutoCheckInterval; d != nil && *d <= 0 {
|
||||||
|
return fmt.Errorf("%s: updates.autoCheckInterval: %s is not positive (autoCheck: false turns it off)", path, *d)
|
||||||
|
}
|
||||||
|
for i, r := range c.Updates.UpdateSources {
|
||||||
if _, err := update.API(r); err != nil || !strings.HasPrefix(r, "https://") {
|
if _, err := update.API(r); err != nil || !strings.HasPrefix(r, "https://") {
|
||||||
return fmt.Errorf("%s: releases[%d]: %q is not an https:// repository address, like %s", path, i, r, update.GitHubRepo)
|
return fmt.Errorf("%s: updates.updateSources[%d]: %q is not an https:// repository address, like %s", path, i, r, update.GitHubRepo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if k := c.Share.Key; k != "" {
|
if k := c.Share.Key; k != "" {
|
||||||
|
|||||||
@@ -70,8 +70,9 @@ func TestLoadRejectsMistakes(t *testing.T) {
|
|||||||
"old layout": "azure:\n vaults: [https://kv.vault.azure.net/]\n",
|
"old layout": "azure:\n vaults: [https://kv.vault.azure.net/]\n",
|
||||||
"duration": "timeout: soon\n",
|
"duration": "timeout: soon\n",
|
||||||
"view": "sourcesView: tree\n",
|
"view": "sourcesView: tree\n",
|
||||||
"releases": "releases: [git.example.com/team/lazysecrets]\n",
|
"sources": "updates:\n updateSources: [git.example.com/team/lazysecrets]\n",
|
||||||
"http": "releases: [http://git.example.com/team/lazysecrets]\n",
|
"http": "updates:\n updateSources: [http://git.example.com/team/lazysecrets]\n",
|
||||||
|
"interval": "updates:\n autoCheckInterval: 0s\n",
|
||||||
"share key": "share:\n key: https://kv.vault.azure.net/\n",
|
"share key": "share:\n key: https://kv.vault.azure.net/\n",
|
||||||
"workspace": "share:\n key: \"{{secrets/team/env-share}}\"\n",
|
"workspace": "share:\n key: \"{{secrets/team/env-share}}\"\n",
|
||||||
"short keys": "share:\n keyRules:\n minLength: 8\n",
|
"short keys": "share:\n keyRules:\n minLength: 8\n",
|
||||||
@@ -119,10 +120,10 @@ theme:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReleases(t *testing.T) {
|
func TestUpdates(t *testing.T) {
|
||||||
c, err := Load(write(t, "releases: [https://git.example.com/team/lazysecrets]\n"))
|
c, err := Load(write(t, "updates:\n autoCheck: false\n autoCheckInterval: 168h\n updateSources: [https://git.example.com/team/lazysecrets]\n"))
|
||||||
if err != nil || len(c.Releases) != 1 {
|
if err != nil || On(c.Updates.AutoCheck) || *c.Updates.AutoCheckInterval != 7*24*time.Hour || len(c.Updates.UpdateSources) != 1 {
|
||||||
t.Errorf("releases = %v, %v", c.Releases, err)
|
t.Errorf("updates = %+v, %v", c.Updates, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,9 +117,11 @@ type Options struct {
|
|||||||
// Keys, if set, replaces the default keymap (keys.Map.Apply).
|
// Keys, if set, replaces the default keymap (keys.Map.Apply).
|
||||||
Keys *keys.Map
|
Keys *keys.Map
|
||||||
// Updater, if set, finds and installs newer releases (U); with
|
// Updater, if set, finds and installs newer releases (U); with
|
||||||
// UpdateCheck, lazysecrets also looks once a day on its own.
|
// UpdateCheck, lazysecrets also looks on its own at startup, at most
|
||||||
|
// once per UpdateEvery (0 for a day).
|
||||||
Updater *Updater
|
Updater *Updater
|
||||||
UpdateCheck bool
|
UpdateCheck bool
|
||||||
|
UpdateEvery time.Duration
|
||||||
// HandlerSetup, if set, offers to set up the link handler at startup.
|
// HandlerSetup, if set, offers to set up the link handler at startup.
|
||||||
HandlerSetup *HandlerSetup
|
HandlerSetup *HandlerSetup
|
||||||
// NoHistory stops counting uses of items for the Recent list.
|
// NoHistory stops counting uses of items for the Recent list.
|
||||||
@@ -236,6 +238,7 @@ type Model struct {
|
|||||||
|
|
||||||
updater *Updater
|
updater *Updater
|
||||||
updateCheck bool
|
updateCheck bool
|
||||||
|
updateEvery time.Duration
|
||||||
fetchOnStart bool // look for a newer release in Init
|
fetchOnStart bool // look for a newer release in Init
|
||||||
newer *update.Release // a newer release to offer
|
newer *update.Release // a newer release to offer
|
||||||
updated string // the release installed, waiting for a restart
|
updated string // the release installed, waiting for a restart
|
||||||
@@ -427,7 +430,10 @@ func New(providers []provider.Provider, opts Options) Model {
|
|||||||
m.revealTimeout, m.hideOnBlur = opts.RevealTimeout, opts.HideOnBlur
|
m.revealTimeout, m.hideOnBlur = opts.RevealTimeout, opts.HideOnBlur
|
||||||
m.expiryWarning = opts.ExpiryWarning
|
m.expiryWarning = opts.ExpiryWarning
|
||||||
m.handlerSetup = opts.HandlerSetup
|
m.handlerSetup = opts.HandlerSetup
|
||||||
m.updater, m.updateCheck = opts.Updater, opts.UpdateCheck
|
m.updater, m.updateCheck, m.updateEvery = opts.Updater, opts.UpdateCheck, opts.UpdateEvery
|
||||||
|
if m.updateEvery == 0 {
|
||||||
|
m.updateEvery = updateEvery
|
||||||
|
}
|
||||||
m.startUpdateCheck()
|
m.startUpdateCheck()
|
||||||
m.askHandler = opts.HandlerSetup != nil && !m.store.Flag(flagNoHandlerPrompt)
|
m.askHandler = opts.HandlerSetup != nil && !m.store.Flag(flagNoHandlerPrompt)
|
||||||
m.settle = opts.LoadDelay
|
m.settle = opts.LoadDelay
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ type Updater struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// updateEvery is how often lazysecrets looks for a newer release on its
|
// updateEvery is how often lazysecrets looks for a newer release on its
|
||||||
// own; within that, the last answer (in the state file) is used.
|
// own unless the config says otherwise; within that, the last answer (in
|
||||||
|
// the state file) is used.
|
||||||
const updateEvery = 24 * time.Hour
|
const updateEvery = 24 * time.Hour
|
||||||
|
|
||||||
type updateMsg struct {
|
type updateMsg struct {
|
||||||
@@ -35,14 +36,14 @@ type installedMsg struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startUpdateCheck sets up the startup look for a newer release: the
|
// startUpdateCheck sets up the startup look for a newer release: the
|
||||||
// state file's answer when it looked less than a day ago, else a quiet
|
// state file's answer when it looked within updateEvery, else a quiet
|
||||||
// look at the server (fetchOnStart, for Init).
|
// look at the server (fetchOnStart, for Init).
|
||||||
func (m *Model) startUpdateCheck() {
|
func (m *Model) startUpdateCheck() {
|
||||||
if m.updater == nil || !m.updateCheck {
|
if m.updater == nil || !m.updateCheck {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
last := m.store.UpdateCheck()
|
last := m.store.UpdateCheck()
|
||||||
if m.now().Sub(last.At) < updateEvery {
|
if m.now().Sub(last.At) < m.updateEvery {
|
||||||
m.offer(update.Release{Tag: last.Tag, Body: last.Notes, URL: last.URL})
|
m.offer(update.Release{Tag: last.Tag, Body: last.Notes, URL: last.URL})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,12 @@ func TestUpdate(t *testing.T) {
|
|||||||
if f2.asked != 1 {
|
if f2.asked != 1 {
|
||||||
t.Errorf("asked %d times a day later", f2.asked)
|
t.Errorf("asked %d times a day later", f2.asked)
|
||||||
}
|
}
|
||||||
|
// With autoCheckInterval: 1h, two hours on is long enough.
|
||||||
|
mem.SetUpdateCheck(store.UpdateCheck{At: time.Now().Add(-2 * time.Hour), Tag: "v0.2.0"})
|
||||||
|
newDriverOpts(t, Options{Version: "0.1.1", Updater: f2.updater(), UpdateCheck: true, UpdateEvery: time.Hour, Store: mem})
|
||||||
|
if f2.asked != 2 {
|
||||||
|
t.Errorf("asked %d times, want a look after the hour", f2.asked)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateAsked(t *testing.T) {
|
func TestUpdateAsked(t *testing.T) {
|
||||||
|
|||||||
Reference in new issue
Block a user