Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package arkade for openSUSE:Factory checked in at 2026-11-06 19:46:00 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/arkade (Old) and /work/SRC/openSUSE:Factory/.arkade.new.3759290 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "arkade" Fri Nov 6 19:46:00 2026 rev:98 rq:1395465 version:0.12.0 Changes: -------- --- /work/SRC/openSUSE:Factory/arkade/arkade.changes 2026-10-27 16:51:20.495469237 +0100 +++ /work/SRC/openSUSE:Factory/.arkade.new.3759290/arkade.changes 2026-11-06 19:51:59.656153425 +0100 @@ -1,0 +2,12 @@ +Thu Nov 05 14:22:09 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 0.12.0: + * Keep opencode2 on the 2.x release line + * Require a redirect for HEAD release lookups + * Reject error status codes when resolving releases + * Add opencode2 CLI alongside opencode + * Document tinygo system install in README + * Add system install for TinyGo + * Drop the consul-connect app + +------------------------------------------------------------------- Old: ---- arkade-0.11.134.obscpio New: ---- arkade-0.12.0.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ arkade.spec ++++++ --- /var/tmp/diff_new_pack.SSkzu7/_old 2026-11-06 19:52:03.614319024 +0100 +++ /var/tmp/diff_new_pack.SSkzu7/_new 2026-11-06 19:52:03.621319317 +0100 @@ -17,7 +17,7 @@ Name: arkade -Version: 0.11.134 +Version: 0.12.0 Release: 0 Summary: Open Source Kubernetes Marketplace License: Apache-2.0 @@ -30,7 +30,7 @@ Source12: PACKAGING_README.md Source13: vendor_go_modules.sh BuildRequires: bash-completion -BuildRequires: golang(API) >= 1.27 +BuildRequires: go1.27 >= 1.27.3 BuildRequires: zsh %description ++++++ _service ++++++ --- /var/tmp/diff_new_pack.SSkzu7/_old 2026-11-06 19:52:03.779325928 +0100 +++ /var/tmp/diff_new_pack.SSkzu7/_new 2026-11-06 19:52:03.784326137 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/alexellis/arkade.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">refs/tags/0.11.134</param> + <param name="revision">refs/tags/0.12.0</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.SSkzu7/_old 2026-11-06 19:52:03.832328145 +0100 +++ /var/tmp/diff_new_pack.SSkzu7/_new 2026-11-06 19:52:03.838328396 +0100 @@ -3,6 +3,6 @@ <param name="url">https://github.com/alexellis/arkade</param> <param name="changesrevision">37af31c7b58de8f16a10067051e3bbe7ecb6aa79</param></service><service name="tar_scm"> <param name="url">https://github.com/alexellis/arkade.git</param> - <param name="changesrevision">8acb4741409717fc99fb8fb4e4a1ecf30d61c05e</param></service></servicedata> + <param name="changesrevision">a5f88e9316f954916ea8bf6fed53254417c2d9d6</param></service></servicedata> (No newline at EOF) ++++++ arkade-0.11.134.obscpio -> arkade-0.12.0.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/.github/CODEOWNERS new/arkade-0.12.0/.github/CODEOWNERS --- old/arkade-0.11.134/.github/CODEOWNERS 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/.github/CODEOWNERS 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -@alexellis diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/.github/FUNDING.yml new/arkade-0.12.0/.github/FUNDING.yml --- old/arkade-0.11.134/.github/FUNDING.yml 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/.github/FUNDING.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -github: alexellis diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/.github/ISSUE_TEMPLATE.md new/arkade-0.12.0/.github/ISSUE_TEMPLATE.md --- old/arkade-0.11.134/.github/ISSUE_TEMPLATE.md 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/.github/ISSUE_TEMPLATE.md 1970-01-01 01:00:00.000000000 +0100 @@ -1,57 +0,0 @@ -<!--- Provide a general summary of the issue in the Title above --> - -## Expected Behaviour -<!--- If you're describing a bug, tell us what should happen --> -<!--- If you're suggesting a change/improvement, tell us how it should work --> - -## Current Behaviour -<!--- If describing a bug, tell us what happens instead of the expected behavior --> -<!--- If suggesting a change/improvement, explain the difference from current behavior --> - -## Possible Solution -<!--- Not obligatory, but suggest a fix/reason for the bug, --> -<!--- or ideas how to implement the addition or change --> - -## Steps to Reproduce (for bugs) -<!--- Provide a link to a live example, or an unambiguous set of steps to --> -<!--- reproduce this bug. Include code to reproduce, if relevant --> -1. -2. -3. -4. - -## Context - -<!--- How has this issue affected you? What are you trying to accomplish? --> -<!--- What would happen if this issue was not resolved for you in some way? --> -<!--- Providing context helps us come up with a solution that is most useful in the real world --> - -## If requesting a CLI for "arkade get" - -How many downloads does this tool have? Check at: - -https://somsubhra.github.io/github-release-stats/ - -If approved, are you willing to submit a pull request for this? - -## Your Environment - -* What Kubernetes distribution are you using? - -``` -kubectl version -``` - -* Operating System and version (e.g. Linux, Windows, MacOS): - -``` -uname -a - -cat /etc/os-release -``` - -* What arkade version is this? - -``` -arkade version -``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/.github/PULL_REQUEST_TEMPLATE.md new/arkade-0.12.0/.github/PULL_REQUEST_TEMPLATE.md --- old/arkade-0.11.134/.github/PULL_REQUEST_TEMPLATE.md 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/.github/PULL_REQUEST_TEMPLATE.md 1970-01-01 01:00:00.000000000 +0100 @@ -1,42 +0,0 @@ -<!--- Provide a general summary of your changes in the Title above --> - -## Description -<!--- Describe your changes in detail --> - -## Motivation and Context -<!--- Why is this change required? What problem does it solve? --> -<!--- If it fixes an open issue, please link to the issue here. --> -- [ ] I have raised an issue to propose this change, which has been given a label of `design/approved` by a maintainer ([required](https://github.com/alexellis/arkade/blob/master/CONTRIBUTING.md)) - - -## How Has This Been Tested? -<!--- Please describe in detail how you tested your changes. --> -<!--- Include details of your testing environment, and the tests you ran to --> -<!--- see how your change affects other areas of the code, etc. --> - -If updating or adding a new CLI to `arkade get`, run: - -``` -go build && ./hack/test-tool.sh TOOL_NAME -``` - -## Types of changes -<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to change) - -## Documentation - -- [ ] I have updated the list of tools in README.md if (required) with `./arkade get --format markdown` -- [ ] I have updated the list of apps in README.md if (required) with `./arkade install --help` - -## Checklist: -- [ ] My code follows the code style of this project. -- [ ] My change requires a change to the documentation. -- [ ] I have updated the documentation accordingly. -- [ ] I've read the [CONTRIBUTION](https://github.com/alexellis/arkade/blob/master/CONTRIBUTING.md) guide -- [ ] I have signed-off my commits with `git commit -s` - -<!--- For "arkade install" or "arkade system install" --> -- [ ] I have tested this on arm, or have added code to prevent deployment diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/.github/dependabot.yml new/arkade-0.12.0/.github/dependabot.yml --- old/arkade-0.11.134/.github/dependabot.yml 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/.github/dependabot.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,11 +0,0 @@ -version: 2 -# reference: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates -updates: - - package-ecosystem: "gomod" - directory: "/" - schedule: - interval: "daily" - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/.github/workflows/build.yml new/arkade-0.12.0/.github/workflows/build.yml --- old/arkade-0.11.134/.github/workflows/build.yml 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/.github/workflows/build.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,27 +0,0 @@ -name: build - -on: - push: - branches: - - master - pull_request: - branches: - - "*" - -jobs: - build: - concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 1 - - name: Install Go - uses: actions/setup-go@v7 - with: - go-version: 1.27.x - - name: Make all - run: make all diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/.github/workflows/e2e-url-checker.yml new/arkade-0.12.0/.github/workflows/e2e-url-checker.yml --- old/arkade-0.11.134/.github/workflows/e2e-url-checker.yml 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/.github/workflows/e2e-url-checker.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,26 +0,0 @@ -name: e2e-url-checker - -on: - schedule: - - cron: "0 0 * * *" - push: - branches: - - master - pull_request: - branches: - - "*" - workflow_dispatch: - -jobs: - url-checker: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 1 - - name: Install Go - uses: actions/setup-go@v7 - with: - go-version: 1.27.x - - name: Run URL checker tests - run: make e2e diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/.github/workflows/publish.yml new/arkade-0.12.0/.github/workflows/publish.yml --- old/arkade-0.11.134/.github/workflows/publish.yml 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/.github/workflows/publish.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,26 +0,0 @@ -name: publish - -on: - push: - tags: - - "*" - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 1 - - name: Install Go - uses: actions/setup-go@v7 - with: - go-version: 1.27.x - - name: Make all - run: make all - - name: Upload release binaries - uses: alexellis/[email protected] - env: - GITHUB_TOKEN: ${{ github.token }} - with: - asset_paths: '["./bin/arkade*"]' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/.gitignore new/arkade-0.12.0/.gitignore --- old/arkade-0.11.134/.gitignore 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/.gitignore 1970-01-01 01:00:00.000000000 +0100 @@ -1,13 +0,0 @@ -arkade -arkade.exe -bin/** -kubeconfig -.DS_Store -.idea/ -mc -/arkade-* -/faas-cli* -test.out -docker-compose.yaml* -TOOLS.md -/.claude/settings.local.json diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/README.md new/arkade-0.12.0/README.md --- old/arkade-0.11.134/README.md 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/README.md 2026-11-05 10:16:51.000000000 +0100 @@ -246,6 +246,9 @@ # with systemd enabled arkade system install containerd \ --systemd + +# Install TinyGo for AMD64 or ARM64 +arkade system install tinygo ``` Run the following to see what's available `arkade system install`: @@ -265,6 +268,7 @@ pwsh Install Powershell registry Install registry tc-redirect-tap Install tc-redirect-tap + tinygo Install TinyGo zvol-snapshotter Install containerd zvol snapshotter ``` @@ -746,7 +750,6 @@ | cert-manager | Install cert-manager | | chart | Install the specified helm chart | | cockroachdb | Install CockroachDB | -| consul-connect | Install Consul Service Mesh | | cron-connector | Install cron-connector for OpenFaaS | | crossplane | Install Crossplane | | docker-registry | Install a community maintained Docker registry chart | @@ -960,6 +963,7 @@ | [op](https://github.com/1password/) | 1Password CLI enables you to automate administrative tasks and securely provision secrets across development environments. | | [opa](https://github.com/open-policy-agent/opa) | General-purpose policy engine that enables unified, context-aware policy enforcement across the entire stack. | | [opencode](https://github.com/anomalyco/opencode) | The opencode CLI for running AI agents and tools. | +| [opencode2](https://github.com/anomalyco/opencode) | The opencode CLI for running AI agents and tools, installed as opencode2. | | [openshift-install](https://github.com/openshift/installer) | CLI to install an OpenShift 4.x cluster. | | [operator-sdk](https://github.com/operator-framework/operator-sdk) | Operator SDK is a tool for scaffolding and generating code for building Kubernetes operators | | [opkssh](https://github.com/openpubkey/opkssh) | A new type of shell that can handle structured data like YAML really well | @@ -1026,5 +1030,5 @@ | [xq](https://github.com/sibprogrammer/xq) | XML to JSON/YAML converter and query tool. | | [yq](https://github.com/mikefarah/yq) | Portable command-line YAML processor. | | [yt-dlp](https://github.com/yt-dlp/yt-dlp) | Fork of youtube-dl with additional features and fixes | -There are 222 tools, use `arkade get NAME` to download one. +There are 223 tools, use `arkade get NAME` to download one. <!-- end of tool list --> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/cmd/apps/consul_app.go new/arkade-0.12.0/cmd/apps/consul_app.go --- old/arkade-0.11.134/cmd/apps/consul_app.go 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/cmd/apps/consul_app.go 1970-01-01 01:00:00.000000000 +0100 @@ -1,140 +0,0 @@ -// Copyright (c) arkade author(s) 2022. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -package apps - -import ( - "crypto/rand" - "encoding/base64" - "fmt" - "github.com/alexellis/arkade/pkg/config" - "strconv" - "strings" - - "github.com/alexellis/arkade/pkg/apps" - "github.com/alexellis/arkade/pkg/types" - - "github.com/alexellis/arkade/pkg/k8s" - - "github.com/alexellis/arkade/pkg" - "github.com/spf13/cobra" -) - -func MakeInstallConsul() *cobra.Command { - var consul = &cobra.Command{ - Use: "consul-connect", - Short: "Install Consul Service Mesh", - Long: `Install Consul Service Mesh to any Kubernetes cluster`, - Example: ` arkade install consul-connect`, - SilenceUsage: true, - } - - consul.Flags().StringP("namespace", "n", "consul-system", "The namespace used for installation") - consul.Flags().Bool("update-repo", true, "Update the helm repo") - consul.Flags().StringP("datacenter", "d", "dc1", "The name of the datacenter that the agents should register as") - consul.Flags().Bool("enable-connect-injector", true, "If true, all the resources necessary for the Connect injector process to run will be installed") - consul.Flags().Bool("enable-tls-encryption", true, "If true, TLS encryption across the cluster to verify authenticity of the Consul servers and clients is enabled") - consul.Flags().Bool("enable-gossip-encryption", true, "If true, Consul's gossip encryption is enabled") - consul.Flags().String("gossip-encryption-key", "", "The gossip encryption key; when empty, a new, random key is generated") - consul.Flags().Bool("manage-system-acls", true, "If true, the ACL tokens and policies for all Consul and consul-k8s components will automatically be managed") - consul.Flags().StringArray("set", []string{}, "Use custom flags or override existing flags \n(example --set image=org/repo:tag)") - - consul.RunE = func(command *cobra.Command, args []string) error { - updateRepo, _ := consul.Flags().GetBool("update-repo") - - arch := k8s.GetNodeArchitecture() - fmt.Printf("Node architecture: %q\n", arch) - - namespace, _ := consul.Flags().GetString("namespace") - - overrides := map[string]string{} - overrides["global.name"] = "consul" - - datacenter, _ := command.Flags().GetString("datacenter") - overrides["global.datacenter"] = datacenter - - connectInjectorEnabled, _ := command.Flags().GetBool("enable-connect-injector") - overrides["connectInject.enabled"] = strings.ToLower(strconv.FormatBool(connectInjectorEnabled)) - - tlsEnabled, _ := command.Flags().GetBool("enable-tls-encryption") - overrides["global.tls.enabled"] = strings.ToLower(strconv.FormatBool(tlsEnabled)) - - manageSystemACLs, _ := command.Flags().GetBool("manage-system-acls") - overrides["global.acls.manageSystemACLs"] = strings.ToLower(strconv.FormatBool(manageSystemACLs)) - - gossipEncryptionEnabled, _ := command.Flags().GetBool("enable-gossip-encryption") - gossipEncryptionKey, _ := command.Flags().GetString("gossip-encryption-key") - - var err error - if gossipEncryptionEnabled && gossipEncryptionKey == "" { - gossipEncryptionKey, err = generateGossipEncryptionKey() - if err != nil { - return err - } - } - - if gossipEncryptionEnabled { - res, err := k8s.KubectlTask("create", "secret", "generic", - "consul-gossip-encryption-key", - "--namespace="+namespace, - "--from-literal", "key="+gossipEncryptionKey) - if err != nil { - return err - } else if len(res.Stderr) > 0 && strings.Contains(res.Stderr, "AlreadyExists") { - fmt.Println("[Warning] secret consul-gossip-encryption-key already exists and will be used.") - } else if len(res.Stderr) > 0 { - return fmt.Errorf("error from kubectl\n%q", res.Stderr) - } - - overrides["global.gossipEncryption.secretName"] = "consul-gossip-encryption-key" - overrides["global.gossipEncryption.secretKey"] = "key" - } - - customFlags, _ := command.Flags().GetStringArray("set") - - if err := config.MergeFlags(overrides, customFlags); err != nil { - return err - } - - kubeConfigPath, _ := command.Flags().GetString("kubeconfig") - - consulOptions := types.DefaultInstallOptions(). - WithNamespace(namespace). - WithHelmRepo("hashicorp/consul"). - WithHelmURL("https://helm.releases.hashicorp.com"). - WithOverrides(overrides). - WithHelmUpdateRepo(updateRepo). - WithKubeconfigPath(kubeConfigPath) - - _, err = apps.MakeInstallChart(consulOptions) - if err != nil { - return err - } - - fmt.Println(consulInstallMsg) - return nil - } - - return consul -} - -func generateGossipEncryptionKey() (string, error) { - key := make([]byte, 32) - n, err := rand.Reader.Read(key) - if err != nil { - return "", err - } - if n != 32 { - return "", fmt.Errorf("couldn't read enough entropy, generate more entropy") - } - - return base64.StdEncoding.EncodeToString(key), nil -} - -const ConsulInfoMsg = `# Find out more at: -# https://www.consul.io/docs/k8s` - -var consulInstallMsg = `======================================================================= -= Consul has been installed. = -=======================================================================` + - "\n\n" + ConsulInfoMsg + "\n\n" + pkg.SupportMessageShort diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/cmd/install.go new/arkade-0.12.0/cmd/install.go --- old/arkade-0.11.134/cmd/install.go 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/cmd/install.go 2026-11-05 10:16:51.000000000 +0100 @@ -101,7 +101,6 @@ arkadeApps["cassandra"] = NewArkadeApp(apps.MakeInstallCassandra, apps.CassandraInfoMsg) arkadeApps["cert-manager"] = NewArkadeApp(apps.MakeInstallCertManager, apps.CertManagerInfoMsg) arkadeApps["cockroachdb"] = NewArkadeApp(apps.MakeInstallCockroachdb, apps.CockroachdbInfoMsg) - arkadeApps["consul-connect"] = NewArkadeApp(apps.MakeInstallConsul, apps.ConsulInfoMsg) arkadeApps["cron-connector"] = NewArkadeApp(apps.MakeInstallCronConnector, apps.CronConnectorInfoMsg) arkadeApps["crossplane"] = NewArkadeApp(apps.MakeInstallCrossplane, apps.CrossplaneInfoMsg) arkadeApps["docker-registry"] = NewArkadeApp(apps.MakeInstallRegistry, apps.RegistryInfoMsg) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/cmd/system/install.go new/arkade-0.12.0/cmd/system/install.go --- old/arkade-0.11.134/cmd/system/install.go 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/cmd/system/install.go 2026-11-05 10:16:51.000000000 +0100 @@ -37,6 +37,7 @@ command.AddCommand(MakeInstallPowershell()) command.AddCommand(MakeInstallCaddyServer()) command.AddCommand(MakeInstallZvolSnapshotter()) + command.AddCommand(MakeInstallTinyGo()) command.AddCommand(MakeInstallNodeExporter()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/cmd/system/tinygo.go new/arkade-0.12.0/cmd/system/tinygo.go --- old/arkade-0.11.134/cmd/system/tinygo.go 1970-01-01 01:00:00.000000000 +0100 +++ new/arkade-0.12.0/cmd/system/tinygo.go 2026-11-05 10:16:51.000000000 +0100 @@ -0,0 +1,113 @@ +// Copyright (c) arkade author(s) 2026. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +package system + +import ( + "fmt" + "os" + "path" + "strings" + + "github.com/alexellis/arkade/pkg/archive" + "github.com/alexellis/arkade/pkg/env" + "github.com/alexellis/arkade/pkg/get" + "github.com/spf13/cobra" +) + +func MakeInstallTinyGo() *cobra.Command { + + command := &cobra.Command{ + Use: "tinygo", + Short: "Install TinyGo", + Long: `Install TinyGo, a Go compiler for microcontrollers, WebAssembly (WASM), and CLI tools.`, + Example: ` arkade system install tinygo + arkade system install tinygo --version 0.43.0`, + SilenceUsage: true, + } + + command.Flags().StringP("version", "v", "", "The version for TinyGo, or leave blank for latest") + command.Flags().String("path", "/usr/local/", "Installation path, where a tinygo subfolder will be created") + command.Flags().Bool("progress", true, "Show download progress") + + command.PreRunE = func(cmd *cobra.Command, args []string) error { + return nil + } + + command.RunE = func(cmd *cobra.Command, args []string) error { + installPath, _ := cmd.Flags().GetString("path") + version, _ := cmd.Flags().GetString("version") + fmt.Printf("Installing TinyGo to %s\n", installPath) + + if err := os.MkdirAll(installPath, 0755); err != nil && !os.IsExist(err) { + fmt.Printf("Error creating directory %s, error: %s\n", installPath, err.Error()) + } + + arch, osVer := env.GetClientArch() + + if strings.ToLower(osVer) != "linux" { + return fmt.Errorf("this app only supports Linux") + } + + dlArch := arch + if arch == "x86_64" { + dlArch = "amd64" + } else if arch == "aarch64" { + dlArch = "arm64" + } + + if dlArch != "amd64" && dlArch != "arm64" { + return fmt.Errorf("this app only supports amd64 and arm64 architectures, found: %s", arch) + } + + if len(version) == 0 { + v, err := get.FindGitHubRelease("tinygo-org", "tinygo") + if err != nil { + return err + } + + version = v + } else if !strings.HasPrefix(version, "v") { + version = "v" + version + } + + fmt.Printf("Installing version: %s for: %s\n", version, dlArch) + + versionNumber := strings.TrimPrefix(version, "v") + downloadName := fmt.Sprintf("tinygo%s.linux-%s.tar.gz", versionNumber, dlArch) + dlURL := fmt.Sprintf(githubDownloadTemplate, "tinygo-org", "tinygo", version, downloadName) + fmt.Printf("Downloading from: %s\n", dlURL) + + progress, _ := cmd.Flags().GetBool("progress") + outPath, err := get.DownloadFileP(dlURL, progress) + if err != nil { + return err + } + defer os.Remove(outPath) + + fmt.Printf("Downloaded to: %s\n", outPath) + + f, err := os.OpenFile(outPath, os.O_RDONLY, 0644) + if err != nil { + return err + } + defer f.Close() + + fmt.Printf("Unpacking TinyGo to: %s\n", path.Join(installPath, "tinygo")) + + if err := spinWhile("Unpacking TinyGo", func() error { + return archive.UntarNested(f, installPath, true, true, true, false) + }); err != nil { + return err + } + + fmt.Printf(`TinyGo installed to: %s + +export PATH=$PATH:%s +`, path.Join(installPath, "tinygo", "bin", "tinygo"), path.Join(installPath, "tinygo", "bin")) + + return nil + } + + return command +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/pkg/get/get.go new/arkade-0.12.0/pkg/get/get.go --- old/arkade-0.11.134/pkg/get/get.go 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/pkg/get/get.go 2026-11-05 10:16:51.000000000 +0100 @@ -3,6 +3,7 @@ import ( "bytes" "crypto/tls" + "encoding/json" "errors" "fmt" "io" @@ -16,6 +17,7 @@ "text/template" "time" + semver "github.com/Masterminds/semver/v3" "github.com/alexellis/arkade/pkg" "github.com/alexellis/arkade/pkg/env" ) @@ -24,6 +26,10 @@ const GitLabVersionStrategy = "gitlab" const k8sVersionStrategy = "k8s" const AmpStrategy = `amp` +const OpenCodeStrategy = "opencode" + +var errUnsupportedOpenCodeVersion = errors.New("unsupported opencode version") +var errUnexpectedReleaseStatus = errors.New("unexpected release response") const HashicorpShasumStrategy = `hashicorp-sha` const ClaudeShasumStrategy = `claude-sha` @@ -61,6 +67,10 @@ } func isPermanentError(err error) bool { + if errors.Is(err, errUnsupportedOpenCodeVersion) || errors.Is(err, errUnexpectedReleaseStatus) { + return true + } + var urlErr *url.Error if errors.As(err, &urlErr) && urlErr.Op == "parse" { return true @@ -158,6 +168,11 @@ Timeout: time.Second * 5, Method: http.MethodGet, }, + OpenCodeStrategy: { + Url: "https://opencode.ai/update/api/latest/cli/npm", + Timeout: time.Second * 10, + Method: http.MethodGet, + }, } type ToolLocal struct { @@ -401,13 +416,18 @@ if releaseLocations[location].Method == http.MethodHead { - if res.StatusCode != http.StatusMovedPermanently && res.StatusCode != http.StatusFound { + // the version is advertised in the Location header of a + // redirect, any other status cannot yield one + if res.StatusCode < 300 || res.StatusCode >= 400 { + if res.StatusCode >= 200 && res.StatusCode < 300 { + return "", fmt.Errorf("%w: server returned status: %d", errUnexpectedReleaseStatus, res.StatusCode) + } return "", fmt.Errorf("server returned status: %d", res.StatusCode) } loc := res.Header.Get("Location") if len(loc) == 0 { - return "", fmt.Errorf("unable to determine release of tool") + return "", fmt.Errorf("unable to determine release of tool, server returned status: %d", res.StatusCode) } version := loc[strings.LastIndex(loc, "/")+1:] @@ -418,12 +438,47 @@ return "", fmt.Errorf("unable to determine release of tool") } + // redirects are not followed (CheckRedirect returns + // ErrUseLastResponse), so only a 2xx response can carry + // the version in its body + if res.StatusCode >= 300 && res.StatusCode < 400 { + return "", fmt.Errorf("%w: server returned status: %d", errUnexpectedReleaseStatus, res.StatusCode) + } + if res.StatusCode < 200 || res.StatusCode >= 300 { + return "", fmt.Errorf("server returned status: %d", res.StatusCode) + } + bodyBytes, err := io.ReadAll(res.Body) if err != nil { return "", err } version := strings.TrimSpace(string(bodyBytes)) + + // The opencode update API returns a JSON document, from + // which we extract the "version" field. + if location == OpenCodeStrategy { + var doc struct { + Version string `json:"version"` + } + if err := json.Unmarshal(bodyBytes, &doc); err != nil { + return "", fmt.Errorf("unable to parse version from opencode update API: %w", err) + } + if len(doc.Version) == 0 { + return "", fmt.Errorf("no version returned from the opencode update API") + } + parsedVersion, err := semver.NewVersion(doc.Version) + if err != nil { + return "", fmt.Errorf("invalid version from the opencode update API: %q: %w", doc.Version, err) + } + if parsedVersion.Major() != 2 { + return "", fmt.Errorf("%w: expected major version 2, got %q", errUnsupportedOpenCodeVersion, doc.Version) + } + // the API returns a bare version, GitHub release tags + // are prefixed with "v" + version = "v" + strings.TrimPrefix(doc.Version, "v") + } + return version, nil }, 10, 100*time.Millisecond) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/pkg/get/get_test.go new/arkade-0.12.0/pkg/get/get_test.go --- old/arkade-0.11.134/pkg/get/get_test.go 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/pkg/get/get_test.go 2026-11-05 10:16:51.000000000 +0100 @@ -3,10 +3,13 @@ import ( "fmt" "net/http" + "net/http/httptest" "os" "path/filepath" "reflect" "sort" + "strings" + "sync/atomic" "testing" ) @@ -10205,6 +10208,58 @@ } } +func Test_DownloadOpencode2(t *testing.T) { + tools := MakeTools() + name := "opencode2" + + tool := getTool(name, tools) + + const toolVersion = "v2.2.5" + + tests := []test{ + { + os: "darwin", + arch: arch64bit, + version: toolVersion, + url: "https://github.com/anomalyco/opencode/releases/download/v2.2.5/opencode-darwin-x64.zip", + }, + { + os: "darwin", + arch: archDarwinARM64, + version: toolVersion, + url: "https://github.com/anomalyco/opencode/releases/download/v2.2.5/opencode-darwin-arm64.zip", + }, + { + os: "linux", + arch: arch64bit, + version: toolVersion, + url: "https://github.com/anomalyco/opencode/releases/download/v2.2.5/opencode-linux-x64.tar.gz", + }, + { + os: "linux", + arch: archARM64, + version: toolVersion, + url: "https://github.com/anomalyco/opencode/releases/download/v2.2.5/opencode-linux-arm64.tar.gz", + }, + { + os: "mingw64_nt-10.0-18362", + arch: arch64bit, + version: toolVersion, + url: "https://github.com/anomalyco/opencode/releases/download/v2.2.5/opencode-windows-x64.zip", + }, + } + + for _, tc := range tests { + got, _, err := tool.GetURL(tc.os, tc.arch, tc.version, false) + if err != nil { + t.Fatal(err) + } + if got != tc.url { + t.Errorf("\nwant: %s\ngot: %s", tc.url, got) + } + } +} + func Test_DownloadClaude(t *testing.T) { tools := MakeTools() name := "claude" @@ -11175,3 +11230,161 @@ }) } } + +func Test_FindReleaseOpenCodeStrategy(t *testing.T) { + orig := releaseLocations[OpenCodeStrategy].Url + defer func() { + loc := releaseLocations[OpenCodeStrategy] + loc.Url = orig + releaseLocations[OpenCodeStrategy] = loc + }() + + t.Run("parses JSON version and prefixes v", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"version":"2.2.5"}`)) + })) + defer srv.Close() + + loc := releaseLocations[OpenCodeStrategy] + loc.Url = srv.URL + releaseLocations[OpenCodeStrategy] = loc + + got, err := FindRelease(OpenCodeStrategy, "anomalyco", "opencode") + if err != nil { + t.Fatal(err) + } + if got != "v2.2.5" { + t.Errorf("want v2.2.5, got %s", got) + } + }) + + t.Run("error status codes fail fast", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "not found", http.StatusNotFound) + })) + defer srv.Close() + + loc := releaseLocations[OpenCodeStrategy] + loc.Url = srv.URL + releaseLocations[OpenCodeStrategy] = loc + + _, err := FindRelease(OpenCodeStrategy, "anomalyco", "opencode") + if err == nil { + t.Fatal("expected an error for a 404 response") + } + if !strings.Contains(err.Error(), "404") { + t.Errorf("expected 404 in error, got: %s", err.Error()) + } + }) + + t.Run("rejects versions outside major 2 without retrying", func(t *testing.T) { + var requests atomic.Int32 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requests.Add(1) + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"version":"3.0.0"}`)) + })) + defer srv.Close() + + loc := releaseLocations[OpenCodeStrategy] + loc.Url = srv.URL + releaseLocations[OpenCodeStrategy] = loc + + _, err := FindRelease(OpenCodeStrategy, "anomalyco", "opencode") + if err == nil || !strings.Contains(err.Error(), "expected major version 2") { + t.Fatalf("expected a major version error, got: %v", err) + } + if got := requests.Load(); got != 1 { + t.Errorf("expected one request, got %d", got) + } + }) + + t.Run("redirects are rejected on GET strategies", func(t *testing.T) { + var requests atomic.Int32 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requests.Add(1) + w.Header().Set("Location", "https://example.com/elsewhere") + w.WriteHeader(http.StatusFound) + w.Write([]byte(`{"version":"2.2.5"}`)) + })) + defer srv.Close() + + loc := releaseLocations[OpenCodeStrategy] + loc.Url = srv.URL + releaseLocations[OpenCodeStrategy] = loc + + _, err := FindRelease(OpenCodeStrategy, "anomalyco", "opencode") + if err == nil { + t.Fatal("expected an error for a redirect response") + } + if !strings.Contains(err.Error(), "302") { + t.Errorf("expected 302 in error, got: %s", err.Error()) + } + if got := requests.Load(); got != 1 { + t.Errorf("expected one request, got %d", got) + } + }) +} + +func Test_FindReleaseRedirectStrategies(t *testing.T) { + orig := releaseLocations[GitHubVersionStrategy].Url + defer func() { + loc := releaseLocations[GitHubVersionStrategy] + loc.Url = orig + releaseLocations[GitHubVersionStrategy] = loc + }() + + setURL := func(u string) { + loc := releaseLocations[GitHubVersionStrategy] + loc.Url = u + releaseLocations[GitHubVersionStrategy] = loc + } + + t.Run("redirect with location yields version", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", "https://github.com/example/tool/releases/tag/v1.2.3") + w.WriteHeader(http.StatusFound) + })) + defer srv.Close() + setURL(srv.URL) + + got, err := FindRelease(GitHubVersionStrategy, "example", "tool") + if err != nil { + t.Fatal(err) + } + if got != "v1.2.3" { + t.Errorf("want v1.2.3, got %s", got) + } + }) + + t.Run("non-redirect with location is rejected", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", "https://github.com/example/tool/releases/tag/v1.2.3") + w.WriteHeader(http.StatusOK) + })) + defer srv.Close() + setURL(srv.URL) + + _, err := FindRelease(GitHubVersionStrategy, "example", "tool") + if err == nil || !strings.Contains(err.Error(), "200") { + t.Fatalf("expected a 200 status error, got: %v", err) + } + }) + + t.Run("error statuses are rejected", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "gone", http.StatusNotFound) + })) + defer srv.Close() + setURL(srv.URL) + + _, err := FindRelease(GitHubVersionStrategy, "example", "tool") + if err == nil { + t.Fatal("expected an error for a 404 response") + } + if !strings.Contains(err.Error(), "404") { + t.Errorf("expected 404 in error, got: %s", err.Error()) + } + }) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/arkade-0.11.134/pkg/get/tools.go new/arkade-0.12.0/pkg/get/tools.go --- old/arkade-0.11.134/pkg/get/tools.go 2026-10-23 15:39:10.000000000 +0200 +++ new/arkade-0.12.0/pkg/get/tools.go 2026-11-05 10:16:51.000000000 +0100 @@ -69,6 +69,35 @@ {{.Name}}-linux-x64.tar.gz {{- end -}}`, }, + // opencode AI CLI, installed under a separate name so it can + // coexist with the "opencode" binary + Tool{ + Owner: "anomalyco", + Repo: "opencode", + Name: "opencode2", + Description: "The opencode CLI for running AI agents and tools, installed as opencode2.", + // The musl assets need a separate musl loader, so use the + // glibc binaries on Linux to run on standard distributions. + URLTemplate: `https://github.com/{{.Owner}}/{{.Repo}}/releases/download/{{.Version}}/{{ if HasPrefix .OS "ming" -}} +opencode-windows-x64.zip +{{- else if eq .OS "darwin" -}} + {{- if or (eq .Arch "arm64") (eq .Arch "aarch64") -}} +opencode-darwin-arm64.zip + {{- else -}} +opencode-darwin-x64.zip + {{- end -}} +{{- else if or (eq .Arch "aarch64") (eq .Arch "arm64") -}} +opencode-linux-arm64.tar.gz +{{- else -}} +opencode-linux-x64.tar.gz +{{- end -}}`, + // the binary inside each archive is named "opencode", + // it is installed under the tool name "opencode2" + BinaryTemplate: `opencode`, + // GitHub's /releases/latest points at the old v1.x line, + // so resolve the 2.x version from the opencode update API + VersionStrategy: OpenCodeStrategy, + }, Tool{ Owner: "nektos", Repo: "act", ++++++ arkade.obsinfo ++++++ --- /var/tmp/diff_new_pack.SSkzu7/_old 2026-11-06 19:52:04.888372328 +0100 +++ /var/tmp/diff_new_pack.SSkzu7/_new 2026-11-06 19:52:04.896372662 +0100 @@ -1,5 +1,5 @@ name: arkade -version: 0.11.134 -mtime: 1792762750 -commit: 8acb4741409717fc99fb8fb4e4a1ecf30d61c05e +version: 0.12.0 +mtime: 1793870211 +commit: a5f88e9316f954916ea8bf6fed53254417c2d9d6 ++++++ gha-bump.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gha-bump/.github/CODEOWNERS new/gha-bump/.github/CODEOWNERS --- old/gha-bump/.github/CODEOWNERS 2026-08-29 09:34:32.000000000 +0200 +++ new/gha-bump/.github/CODEOWNERS 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -@alexellis diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gha-bump/.github/dependabot.yml new/gha-bump/.github/dependabot.yml --- old/gha-bump/.github/dependabot.yml 2026-08-29 09:34:32.000000000 +0200 +++ new/gha-bump/.github/dependabot.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,11 +0,0 @@ -version: 2 -# reference: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates -updates: - - package-ecosystem: "gomod" - directory: "/" - schedule: - interval: "daily" - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gha-bump/.github/workflows/build.yml new/gha-bump/.github/workflows/build.yml --- old/gha-bump/.github/workflows/build.yml 2026-08-29 09:34:32.000000000 +0200 +++ new/gha-bump/.github/workflows/build.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,27 +0,0 @@ -name: build - -on: - push: - branches: - - '*' - pull_request: - branches: - - '*' - -jobs: - build: - concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 1 - - name: Install Go - uses: actions/setup-go@v7 - with: - go-version: 1.25.x - - name: Make all - run: make all diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gha-bump/.github/workflows/publish.yml new/gha-bump/.github/workflows/publish.yml --- old/gha-bump/.github/workflows/publish.yml 2026-08-29 09:34:32.000000000 +0200 +++ new/gha-bump/.github/workflows/publish.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,37 +0,0 @@ -name: publish - -on: - push: - tags: - - '*' - -permissions: - contents: write - checks: write - - actions: read - issues: read - packages: write - pull-requests: read - repository-projects: read - statuses: read - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 1 - - name: Install Go - uses: actions/setup-go@v7 - with: - go-version: 1.25.x - - name: Make all - run: make all - - name: Upload release binaries - uses: alexellis/[email protected] - env: - GITHUB_TOKEN: ${{ github.token }} - with: - asset_paths: '["./bin/gha-bump*"]' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gha-bump/.gitignore new/gha-bump/.gitignore --- old/gha-bump/.gitignore 2026-08-29 09:34:32.000000000 +0200 +++ new/gha-bump/.gitignore 1970-01-01 01:00:00.000000000 +0100 @@ -1,2 +0,0 @@ -/gha-bump -/bin/** ++++++ vendor.tar.gz ++++++
