feat: toml config support (#9)

## What's new
- TOML config support 
- add config init command to justfile
https://github.com/versenilvis/IRIS/pull/9/commits/a5bb6c23d74ca73385b3590d2eb275ce6429906b
- add copy binary file to local bin on reload
https://github.com/versenilvis/IRIS/pull/9/commits/4da3acdbc58c28f20fa0021d15fec82823ba392b
- init config on setup
https://github.com/versenilvis/IRIS/pull/9/commits/fb48b6bb832f38e24fab9211d92858bab9a58a07

## Fix
- add lsof to support macos because /proc filesystem is Linux-specific
and does not exist on macOS 0439d829573864fcf902fc4127f7d45384bfab01
- git push doesnt suggest current branch, fix
https://github.com/versenilvis/IRIS/pull/9/commits/b498532b401b764a4a1a2c70b3cb44b7caf72320
- use timeout context for lsof command in reload handler
https://github.com/versenilvis/IRIS/pull/9/commits/1a2baee951e6538a729da12aedfe53960e51102d
This commit is contained in:
VERSE
2026-05-30 22:05:06 +07:00
committed by GitHub
parent 8556fa626a
commit 3d76a3ee88
21 changed files with 1064 additions and 160 deletions
+41 -63
View File
@@ -7,23 +7,14 @@ import (
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/versenilvis/iris/config"
)
// updateState holds the persistent update notification state on disk
type updateState struct {
// seenVersion is the last version the user was notified about.
// when a newer release than this is found, we show the message again
SeenVersion string `json:"seen_version"`
// lastCheck is unix timestamp of the last network check
LastCheck int64 `json:"last_check"`
}
// updateResult is passed from the async checker to the main loop
type updateResult struct {
latestVersion string
@@ -33,49 +24,18 @@ type updateResult struct {
// pendingUpdate is set by the background goroutine and consumed once after the first IRIS_CMD_STOP
var pendingUpdate chan updateResult
func getUpdateStateFile() string {
home, err := os.UserHomeDir()
if err != nil {
return ""
}
return filepath.Join(home, ".iris", "update_state.json")
}
func LoadUpdateState() updateState {
file := getUpdateStateFile()
if file == "" {
return updateState{}
}
data, err := os.ReadFile(file)
if err != nil {
return updateState{}
}
var s updateState
if err := json.Unmarshal(data, &s); err != nil {
return updateState{}
}
return s
}
func SaveUpdateState(s updateState) {
file := getUpdateStateFile()
if file == "" {
return
}
data, _ := json.MarshalIndent(s, "", " ")
_ = os.MkdirAll(filepath.Dir(file), 0755)
_ = os.WriteFile(file, data, 0644)
}
// FetchLatestVersion hits the GitHub Releases API and returns the latest tag name
func FetchLatestVersion() (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// allow overriding the version endpoint for testing without a real release
endpoint := os.Getenv("IRIS_UPDATE_URL")
if endpoint == "" {
endpoint = "https://api.github.com/repos/versenilvis/iris/releases/latest"
if config.Get().Updater.Channel == "nightly" {
endpoint = "https://api.github.com/repos/versenilvis/iris/releases"
} else {
endpoint = "https://api.github.com/repos/versenilvis/iris/releases/latest"
}
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
@@ -99,6 +59,19 @@ func FetchLatestVersion() (string, error) {
return "", err
}
if config.Get().Updater.Channel == "nightly" && os.Getenv("IRIS_UPDATE_URL") == "" {
var releases []struct {
TagName string `json:"tag_name"`
}
if err := json.Unmarshal(body, &releases); err != nil {
return "", err
}
if len(releases) == 0 {
return "", fmt.Errorf("no releases found")
}
return releases[0].TagName, nil
}
var result struct {
TagName string `json:"tag_name"`
}
@@ -123,7 +96,7 @@ func IsNewer(current, latest string) bool {
}
// nightly builds are never shown as stable update targets
if strings.Contains(l, "-nightly.") {
if config.Get().Updater.Channel != "nightly" && strings.Contains(l, "-nightly.") {
return false
}
@@ -161,6 +134,11 @@ func IsNewer(current, latest string) bool {
func startBackgroundUpdateCheck() chan updateResult {
ch := make(chan updateResult, 1)
if !config.Get().Updater.CheckOnStartup {
close(ch)
return ch
}
go func() {
defer func() {
if r := recover(); r != nil {
@@ -181,13 +159,13 @@ func startBackgroundUpdateCheck() chan updateResult {
return
}
state := LoadUpdateState()
state := config.LoadState()
// only check once every 6 hours to avoid hammering the API
if time.Since(time.Unix(state.LastCheck, 0)) < 6*time.Hour {
// only check once every configured check-interval to avoid hammering the API
if time.Since(state.Updater.LastCheckTime) < time.Duration(config.Get().Updater.CheckInterval) {
// already checked recently; still notify if we have a cached pending update
if state.SeenVersion != "" && IsNewer(Version, state.SeenVersion) {
ch <- updateResult{latestVersion: state.SeenVersion, hasUpdate: true}
if state.Updater.SeenVersion != "" && IsNewer(Version, state.Updater.SeenVersion) {
ch <- updateResult{latestVersion: state.Updater.SeenVersion, hasUpdate: true}
}
return
}
@@ -199,22 +177,22 @@ func startBackgroundUpdateCheck() chan updateResult {
}
// update the last check time regardless of result
state.LastCheck = time.Now().Unix()
state.Updater.LastCheckTime = time.Now()
if IsNewer(Version, latest) {
// only notify if user hasn't already seen this specific version notification
if state.SeenVersion != latest {
if state.Updater.SeenVersion != latest {
ch <- updateResult{latestVersion: latest, hasUpdate: true}
}
// save the latest as seen_version so future sessions don't re-notify
// unless a NEWER version comes out (different tag)
state.SeenVersion = latest
state.Updater.SeenVersion = latest
} else {
// up to date: clear the seen_version flag so the next update triggers a fresh notification
state.SeenVersion = ""
state.Updater.SeenVersion = ""
}
SaveUpdateState(state)
_ = config.SaveState(state)
}()
return ch
@@ -256,9 +234,9 @@ var updateCmd = &cobra.Command{
if !IsNewer(Version, latest) {
fmt.Printf("\033[32m[IRIS] already up to date (%s)\033[0m\n", Version)
// clear seen_version so the notification doesn't show again
state := LoadUpdateState()
state.SeenVersion = ""
SaveUpdateState(state)
state := config.LoadState()
state.Updater.SeenVersion = ""
_ = config.SaveState(state)
return
}
@@ -269,9 +247,9 @@ var updateCmd = &cobra.Command{
fmt.Printf("running: curl -sS %s | sh\n\n", installScript)
// after a successful update, mark as seen so no more notifications
state := LoadUpdateState()
state.SeenVersion = ""
SaveUpdateState(state)
state := config.LoadState()
state.Updater.SeenVersion = ""
_ = config.SaveState(state)
fmt.Printf("\n\033[32m[IRIS] restart your terminal to use the new version\033[0m\n")
},