2026-02-01 12:06:49 +00:00
|
|
|
package updater
|
|
|
|
|
|
|
|
|
|
import (
|
feat: infrastructure packages and lint cleanup (#281)
* ci: consolidate duplicate workflows and merge CodeQL configs
Remove 17 duplicate workflow files that were split copies of the
combined originals. Each family (CI, CodeQL, Coverage, PR Build,
Alpha Release) had the same job duplicated across separate
push/pull_request/schedule/manual trigger files.
Merge codeql.yml and codescan.yml into a single codeql.yml with
a language matrix covering go, javascript-typescript, python,
and actions — matching the previous default setup coverage.
Remaining workflows (one per family):
- ci.yml (push + PR + manual)
- codeql.yml (push + PR + schedule, all languages)
- coverage.yml (push + PR + manual)
- alpha-release.yml (push + manual)
- pr-build.yml (PR + manual)
- release.yml (tag push)
- agent-verify.yml, auto-label.yml, auto-project.yml
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add collect, config, crypt, plugin packages and fix all lint issues
Add four new infrastructure packages with CLI commands:
- pkg/config: layered configuration (defaults → file → env → flags)
- pkg/crypt: crypto primitives (Argon2id, AES-GCM, ChaCha20, HMAC, checksums)
- pkg/plugin: plugin system with GitHub-based install/update/remove
- pkg/collect: collection subsystem (GitHub, BitcoinTalk, market, papers, excavate)
Fix all golangci-lint issues across the entire codebase (~100 errcheck,
staticcheck SA1012/SA1019/ST1005, unused, ineffassign fixes) so that
`core go qa` passes with 0 issues.
Closes #167, #168, #170, #250, #251, #252, #253, #254, #255, #256
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:34:43 +00:00
|
|
|
"context"
|
2026-02-01 12:06:49 +00:00
|
|
|
"fmt"
|
2026-02-05 11:00:49 +00:00
|
|
|
"os"
|
2026-02-01 12:06:49 +00:00
|
|
|
"runtime"
|
|
|
|
|
|
|
|
|
|
"github.com/host-uk/core/pkg/cli"
|
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Repository configuration for updates
|
|
|
|
|
const (
|
|
|
|
|
repoOwner = "host-uk"
|
|
|
|
|
repoName = "core"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Command flags
|
|
|
|
|
var (
|
2026-02-01 12:14:51 +00:00
|
|
|
updateChannel string
|
|
|
|
|
updateForce bool
|
|
|
|
|
updateCheck bool
|
|
|
|
|
updateWatchPID int
|
2026-02-01 12:06:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
cli.RegisterCommands(AddUpdateCommands)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AddUpdateCommands registers the update command and subcommands.
|
|
|
|
|
func AddUpdateCommands(root *cobra.Command) {
|
|
|
|
|
updateCmd := &cobra.Command{
|
|
|
|
|
Use: "update",
|
|
|
|
|
Short: "Update core CLI to the latest version",
|
|
|
|
|
Long: `Update the core CLI to the latest version from GitHub releases.
|
|
|
|
|
|
|
|
|
|
By default, checks the 'stable' channel for tagged releases (v*.*.*)
|
|
|
|
|
Use --channel=dev for the latest development build.
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
core update # Update to latest stable release
|
|
|
|
|
core update --check # Check for updates without applying
|
|
|
|
|
core update --channel=dev # Update to latest dev build
|
|
|
|
|
core update --force # Force update even if already on latest`,
|
|
|
|
|
RunE: runUpdate,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateCmd.PersistentFlags().StringVar(&updateChannel, "channel", "stable", "Release channel: stable, beta, alpha, or dev")
|
|
|
|
|
updateCmd.PersistentFlags().BoolVar(&updateForce, "force", false, "Force update even if already on latest version")
|
|
|
|
|
updateCmd.Flags().BoolVar(&updateCheck, "check", false, "Only check for updates, don't apply")
|
2026-02-01 12:14:51 +00:00
|
|
|
updateCmd.Flags().IntVar(&updateWatchPID, "watch-pid", 0, "Internal: watch for parent PID to die then restart")
|
|
|
|
|
_ = updateCmd.Flags().MarkHidden("watch-pid")
|
2026-02-01 12:06:49 +00:00
|
|
|
|
|
|
|
|
updateCmd.AddCommand(&cobra.Command{
|
|
|
|
|
Use: "check",
|
|
|
|
|
Short: "Check for available updates",
|
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
|
updateCheck = true
|
|
|
|
|
return runUpdate(cmd, args)
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
root.AddCommand(updateCmd)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runUpdate(cmd *cobra.Command, args []string) error {
|
2026-02-01 12:14:51 +00:00
|
|
|
// If we're in watch mode, wait for parent to die then restart
|
|
|
|
|
if updateWatchPID > 0 {
|
|
|
|
|
return watchAndRestart(updateWatchPID)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 12:06:49 +00:00
|
|
|
currentVersion := cli.AppVersion
|
|
|
|
|
|
|
|
|
|
cli.Print("%s %s\n", cli.DimStyle.Render("Current version:"), cli.ValueStyle.Render(currentVersion))
|
|
|
|
|
cli.Print("%s %s/%s\n", cli.DimStyle.Render("Platform:"), runtime.GOOS, runtime.GOARCH)
|
|
|
|
|
cli.Print("%s %s\n\n", cli.DimStyle.Render("Channel:"), updateChannel)
|
|
|
|
|
|
|
|
|
|
// Handle dev channel specially - it's a prerelease tag, not a semver channel
|
|
|
|
|
if updateChannel == "dev" {
|
|
|
|
|
return handleDevUpdate(currentVersion)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for newer version
|
|
|
|
|
release, updateAvailable, err := CheckForNewerVersion(repoOwner, repoName, updateChannel, true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return cli.Wrap(err, "failed to check for updates")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if release == nil {
|
|
|
|
|
cli.Print("%s No releases found in %s channel\n", cli.WarningStyle.Render("!"), updateChannel)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !updateAvailable && !updateForce {
|
|
|
|
|
cli.Print("%s Already on latest version (%s)\n",
|
|
|
|
|
cli.SuccessStyle.Render(cli.Glyph(":check:")),
|
|
|
|
|
release.TagName)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cli.Print("%s %s\n", cli.DimStyle.Render("Latest version:"), cli.SuccessStyle.Render(release.TagName))
|
|
|
|
|
|
|
|
|
|
if updateCheck {
|
|
|
|
|
if updateAvailable {
|
|
|
|
|
cli.Print("\n%s Update available: %s → %s\n",
|
|
|
|
|
cli.WarningStyle.Render("!"),
|
|
|
|
|
currentVersion,
|
|
|
|
|
release.TagName)
|
|
|
|
|
cli.Print("Run %s to update\n", cli.ValueStyle.Render("core update"))
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 12:14:51 +00:00
|
|
|
// Spawn watcher before applying update
|
|
|
|
|
if err := spawnWatcher(); err != nil {
|
|
|
|
|
// If watcher fails, continue anyway - update will still work
|
|
|
|
|
cli.Print("%s Could not spawn restart watcher: %v\n", cli.DimStyle.Render("!"), err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 12:06:49 +00:00
|
|
|
// Apply update
|
|
|
|
|
cli.Print("\n%s Downloading update...\n", cli.DimStyle.Render("→"))
|
|
|
|
|
|
|
|
|
|
downloadURL, err := GetDownloadURL(release, "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return cli.Wrap(err, "failed to get download URL")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := DoUpdate(downloadURL); err != nil {
|
|
|
|
|
return cli.Wrap(err, "failed to apply update")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cli.Print("%s Updated to %s\n", cli.SuccessStyle.Render(cli.Glyph(":check:")), release.TagName)
|
2026-02-01 12:14:51 +00:00
|
|
|
cli.Print("%s Restarting...\n", cli.DimStyle.Render("→"))
|
2026-02-01 12:06:49 +00:00
|
|
|
|
2026-02-05 11:00:49 +00:00
|
|
|
// Exit so the watcher can restart us
|
|
|
|
|
os.Exit(0)
|
2026-02-01 12:14:51 +00:00
|
|
|
return nil
|
2026-02-01 12:06:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// handleDevUpdate handles updates from the dev release (rolling prerelease)
|
|
|
|
|
func handleDevUpdate(currentVersion string) error {
|
|
|
|
|
client := NewGithubClient()
|
|
|
|
|
|
|
|
|
|
// Fetch the dev release directly by tag
|
feat: infrastructure packages and lint cleanup (#281)
* ci: consolidate duplicate workflows and merge CodeQL configs
Remove 17 duplicate workflow files that were split copies of the
combined originals. Each family (CI, CodeQL, Coverage, PR Build,
Alpha Release) had the same job duplicated across separate
push/pull_request/schedule/manual trigger files.
Merge codeql.yml and codescan.yml into a single codeql.yml with
a language matrix covering go, javascript-typescript, python,
and actions — matching the previous default setup coverage.
Remaining workflows (one per family):
- ci.yml (push + PR + manual)
- codeql.yml (push + PR + schedule, all languages)
- coverage.yml (push + PR + manual)
- alpha-release.yml (push + manual)
- pr-build.yml (PR + manual)
- release.yml (tag push)
- agent-verify.yml, auto-label.yml, auto-project.yml
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add collect, config, crypt, plugin packages and fix all lint issues
Add four new infrastructure packages with CLI commands:
- pkg/config: layered configuration (defaults → file → env → flags)
- pkg/crypt: crypto primitives (Argon2id, AES-GCM, ChaCha20, HMAC, checksums)
- pkg/plugin: plugin system with GitHub-based install/update/remove
- pkg/collect: collection subsystem (GitHub, BitcoinTalk, market, papers, excavate)
Fix all golangci-lint issues across the entire codebase (~100 errcheck,
staticcheck SA1012/SA1019/ST1005, unused, ineffassign fixes) so that
`core go qa` passes with 0 issues.
Closes #167, #168, #170, #250, #251, #252, #253, #254, #255, #256
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:34:43 +00:00
|
|
|
release, err := client.GetLatestRelease(context.TODO(), repoOwner, repoName, "beta")
|
2026-02-01 12:06:49 +00:00
|
|
|
if err != nil {
|
|
|
|
|
// Try fetching the "dev" tag directly
|
|
|
|
|
return handleDevTagUpdate(currentVersion)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if release == nil {
|
|
|
|
|
return handleDevTagUpdate(currentVersion)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cli.Print("%s %s\n", cli.DimStyle.Render("Latest dev:"), cli.ValueStyle.Render(release.TagName))
|
|
|
|
|
|
|
|
|
|
if updateCheck {
|
|
|
|
|
cli.Print("\nRun %s to update\n", cli.ValueStyle.Render("core update --channel=dev"))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 12:14:51 +00:00
|
|
|
// Spawn watcher before applying update
|
|
|
|
|
if err := spawnWatcher(); err != nil {
|
|
|
|
|
cli.Print("%s Could not spawn restart watcher: %v\n", cli.DimStyle.Render("!"), err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 12:06:49 +00:00
|
|
|
cli.Print("\n%s Downloading update...\n", cli.DimStyle.Render("→"))
|
|
|
|
|
|
|
|
|
|
downloadURL, err := GetDownloadURL(release, "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return cli.Wrap(err, "failed to get download URL")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := DoUpdate(downloadURL); err != nil {
|
|
|
|
|
return cli.Wrap(err, "failed to apply update")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cli.Print("%s Updated to %s\n", cli.SuccessStyle.Render(cli.Glyph(":check:")), release.TagName)
|
2026-02-01 12:14:51 +00:00
|
|
|
cli.Print("%s Restarting...\n", cli.DimStyle.Render("→"))
|
2026-02-01 12:06:49 +00:00
|
|
|
|
2026-02-05 11:00:49 +00:00
|
|
|
os.Exit(0)
|
2026-02-01 12:14:51 +00:00
|
|
|
return nil
|
2026-02-01 12:06:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// handleDevTagUpdate fetches the dev release using the direct tag
|
|
|
|
|
func handleDevTagUpdate(currentVersion string) error {
|
|
|
|
|
// Construct download URL directly for dev release
|
|
|
|
|
downloadURL := fmt.Sprintf(
|
|
|
|
|
"https://github.com/%s/%s/releases/download/dev/core-%s-%s",
|
|
|
|
|
repoOwner, repoName, runtime.GOOS, runtime.GOARCH,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
|
downloadURL += ".exe"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cli.Print("%s dev (rolling)\n", cli.DimStyle.Render("Latest:"))
|
|
|
|
|
|
|
|
|
|
if updateCheck {
|
|
|
|
|
cli.Print("\nRun %s to update\n", cli.ValueStyle.Render("core update --channel=dev"))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 12:14:51 +00:00
|
|
|
// Spawn watcher before applying update
|
|
|
|
|
if err := spawnWatcher(); err != nil {
|
|
|
|
|
cli.Print("%s Could not spawn restart watcher: %v\n", cli.DimStyle.Render("!"), err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 12:06:49 +00:00
|
|
|
cli.Print("\n%s Downloading from dev release...\n", cli.DimStyle.Render("→"))
|
|
|
|
|
|
|
|
|
|
if err := DoUpdate(downloadURL); err != nil {
|
|
|
|
|
return cli.Wrap(err, "failed to apply update")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cli.Print("%s Updated to latest dev build\n", cli.SuccessStyle.Render(cli.Glyph(":check:")))
|
2026-02-01 12:14:51 +00:00
|
|
|
cli.Print("%s Restarting...\n", cli.DimStyle.Render("→"))
|
2026-02-01 12:06:49 +00:00
|
|
|
|
2026-02-05 11:00:49 +00:00
|
|
|
os.Exit(0)
|
2026-02-01 12:14:51 +00:00
|
|
|
return nil
|
2026-02-01 12:09:32 +00:00
|
|
|
}
|