-
Notifications
You must be signed in to change notification settings - Fork 0
Build/auto update #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,31 @@ | ||
| # anfra | ||
|
|
||
| Local-first agentic analytics infrastructure — a single binary that runs an | ||
| AML/AQL engine and query layer against your data warehouse. | ||
|
|
||
| ## Install | ||
|
|
||
| ```sh | ||
| curl -fsSL https://raw.githubusercontent.com/holistics/anfra/main/install.sh | bash | ||
| ``` | ||
|
|
||
| The installer downloads the latest release for your platform, places the `anfra` | ||
| binary in `~/.anfra/bin`, and prints the line to add it to your `PATH`. | ||
|
|
||
| Supported platforms: linux (x64/arm64) and macOS (x64/arm64). | ||
|
|
||
| You can configure the installer with environment variables: | ||
|
|
||
| - `ANFRA_INSTALL_DIR` — install somewhere else (default: `~/.anfra/bin`) | ||
| - `ANFRA_VERSION` — install a specific version, e.g. `0.1.0` (default: latest) | ||
|
|
||
| ## Updating | ||
|
|
||
| ```sh | ||
| anfra update # replace the binary with the latest release | ||
| anfra update --check # check for a newer release without installing | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| Run `anfra --help` for commands, or `anfra <command> --help` for a specific one. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| //go:build !windows | ||
|
|
||
| package main | ||
|
|
||
| import ( | ||
| "os/exec" | ||
| "syscall" | ||
| ) | ||
|
|
||
| // detachProcess puts the child in its own process group so it isn't killed when | ||
| // the foreground anfra process (and its group) exits. | ||
| func detachProcess(cmd *exec.Cmd) { | ||
| cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,149 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| package main | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "context" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "io" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "os/exec" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/holistics/anfra/internal/meta" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/holistics/anfra/internal/update" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/spf13/cobra" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // newUpdateCmd is the manual self-update command (like `gh`/`deno upgrade`): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // check the latest GitHub release and, unless --check, replace this binary. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| func newUpdateCmd() *cobra.Command { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| var checkOnly bool | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd := &cobra.Command{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Use: "update", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Short: "Update anfra to the latest release (use --check to only report)", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RunE: func(_ *cobra.Command, _ []string) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return runUpdate(checkOnly) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd.Flags().BoolVar(&checkOnly, "check", false, "only check for an update; do not install") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return cmd | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+17
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pass the command's context
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| func runUpdate(checkOnly bool) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Root at Background (repo convention, see host.go/runServe); timeouts are | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // bounded per-request inside the update package (a quick lookup, then a large | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // download). | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx := context.Background() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| rel, err := update.Latest(ctx) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Record the check so the background notice stays quiet right after. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| update.RecordCheck(rel) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if !rel.IsNewer() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Printf("anfra is up to date (%s).\n", meta.Version) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if checkOnly { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Printf("Update available: %s (you have %s). Run `anfra update` to install.\n", rel.Tag, meta.Version) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Printf("Downloading %s (~250 MB)...\n", rel.Tag) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Show a progress line only on an interactive terminal; stay silent when | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // output is piped/captured (agent, CI). | ||||||||||||||||||||||||||||||||||||||||||||||||||
| var progress io.Writer | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if stderrIsInteractive() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| progress = os.Stderr | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := update.Apply(ctx, rel, progress); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Printf("Updated anfra %s -> %s.\n", meta.Version, rel.Tag) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // newUpdateCheckCmd is a hidden command run detached in the background to | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // refresh the cached update check without blocking the foreground command. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| func newUpdateCheckCmd() *cobra.Command { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return &cobra.Command{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Use: "__update-check", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Hidden: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RunE: func(_ *cobra.Command, _ []string) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return update.Refresh(context.Background()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // commands for which the background update notice is suppressed (they either | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // do their own checking or are long-running/internal). | ||||||||||||||||||||||||||||||||||||||||||||||||||
| var noNotifyCommands = map[string]bool{"update": true, "__update-check": true, "serve": true} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // updateNotifyDisabled reports whether the background update notice is opted out. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| func updateNotifyDisabled() bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return os.Getenv("ANFRA_NO_UPDATE_NOTIFIER") != "" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // autoUpdateEnabled reports whether opt-in fully-automatic update is on. When set, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // a known-newer version is applied in a detached background process (effective on | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // the next run) instead of only printing a notice. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| func autoUpdateEnabled() bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| v := os.Getenv("ANFRA_AUTO_UPDATE") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return v != "" && v != "0" && v != "false" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // maybeNotifyUpdate prints a cached "update available" notice (to stderr, so it | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // never pollutes command output). If opt-in auto-update is on, it instead applies | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // the update in a detached background process. When the cache is stale it spawns a | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // detached refresh so the next run's notice is current. Best-effort and silent on | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // any error — an update check must never break a command. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| func maybeNotifyUpdate(invoked string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if updateNotifyDisabled() || noNotifyCommands[invoked] { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| notice := update.CachedNotice() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Opt-in auto-update is explicit, so it runs regardless of interactivity | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // (e.g. a service that set ANFRA_AUTO_UPDATE=1). | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if notice != "" && autoUpdateEnabled() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Fprintln(os.Stderr, "\n"+notice+" (auto-updating in the background)") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| spawnDetached("update") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // The passive notice and its background refresh are for interactive humans | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // only. When output is piped/captured — an agent calling anfra, CI, a script — | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // stay completely silent and spawn nothing, so we add no noise or overhead. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if !stderrIsInteractive() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if notice != "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Fprintln(os.Stderr, "\n"+notice) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if update.Stale() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| spawnDetached("__update-check") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // stderrIsInteractive reports whether stderr is a terminal (not a pipe/file). | ||||||||||||||||||||||||||||||||||||||||||||||||||
| func stderrIsInteractive() bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fi, err := os.Stderr.Stat() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return fi.Mode()&os.ModeCharDevice != 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // spawnDetached launches `anfra <args...>` detached from this process so it | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // survives after the foreground command exits (the update-notifier pattern). | ||||||||||||||||||||||||||||||||||||||||||||||||||
| func spawnDetached(args ...string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exe, err := os.Executable() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd := exec.Command(exe, args...) //nolint:gosec // fixed args, our own binary | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd.Stdin, cmd.Stdout, cmd.Stderr = nil, nil, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||
| detachProcess(cmd) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := cmd.Start(); err == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| _ = cmd.Process.Release() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,18 @@ | ||
| module github.com/holistics/anfra | ||
|
|
||
| go 1.25 | ||
| go 1.25.0 | ||
|
|
||
| require ( | ||
| github.com/minio/selfupdate v0.6.0 | ||
| github.com/spf13/cobra v1.10.2 | ||
| golang.org/x/mod v0.38.0 | ||
| gopkg.in/yaml.v3 v3.0.1 | ||
| ) | ||
|
|
||
| require ( | ||
| aead.dev/minisign v0.2.0 // indirect | ||
| github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||
| github.com/spf13/pflag v1.0.9 // indirect | ||
| golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect | ||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file is configured with
//go:build !windows, but there is no Windows implementation ofdetachProcess. This will cause compilation to fail on Windows withundefined: detachProcess.To support Windows compilation (even if Windows builds are not officially released yet), please add a
detach_windows.gofile with a stub or Windows-specific implementation: