From a3079b38d096d92e54954198ba27231e212b6252 Mon Sep 17 00:00:00 2001
From: Victor Hugo dos Santos
Date: Mon, 15 Jun 2026 22:46:24 -0300
Subject: [PATCH 1/3] Update README and CLI to reflect migration of Linux
package repository from Cloudsmith
- Added a notice in the README about the migration to `artifacts-cli.infisical.com` and the impending shutdown of Cloudsmith.
- Implemented a one-time daily notice in the CLI for users regarding the repository migration, including a link to the migration guide.
- Introduced caching for the migration notice to prevent repeated prompts within a 24-hour period.
- Added a manual test for the migration notice display functionality.
---
README.md | 3 +
packages/cmd/root.go | 2 +-
packages/util/check-for-update.go | 89 ++++++++++++++++---
packages/util/constants.go | 3 +-
packages/util/migration_notice_manual_test.go | 16 ++++
5 files changed, 97 insertions(+), 16 deletions(-)
create mode 100644 packages/util/migration_notice_manual_test.go
diff --git a/README.md b/README.md
index 8e03f0e1..42e366fe 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,9 @@
The official Infisical CLI: Inject secrets into applications and manage your Infisical infrastructure.
+> [!IMPORTANT]
+> **The Linux package repository has moved off Cloudsmith.** We've migrated the Infisical CLI Linux package repository to our own host at `artifacts-cli.infisical.com`. Cloudsmith shuts down on **[DATE]**; after that, installs and updates from the old URL will fail. Every release, including older versions, is already on the new host. Reinstall the CLI by following the [migration steps]([MIGRATION_GUIDE_URL]) to repoint your machine.
+
## Introduction
The **[Infisical CLI](https://infisical.com/docs/cli/overview)** is a powerful command-line tool for secret management that allows you to:
diff --git a/packages/cmd/root.go b/packages/cmd/root.go
index de1b0b82..e690ed6c 100644
--- a/packages/cmd/root.go
+++ b/packages/cmd/root.go
@@ -132,9 +132,9 @@ func init() {
config.INFISICAL_URL = util.AppendAPIEndpoint(resolveDomain(cmd, config.INFISICAL_URL))
- // util.DisplayAptInstallationChangeBannerWithWriter(silent, cmd.ErrOrStderr())
if !util.IsRunningInDocker() && !silent && !isStructuredOutputRequested(cmd) {
util.CheckForUpdateWithWriter(cmd.ErrOrStderr())
+ util.DisplayPackageRepoMigrationNoticeWithWriter(silent, cmd.ErrOrStderr())
}
loggedInDetails, err := util.GetCurrentLoggedInUserDetails(false)
diff --git a/packages/util/check-for-update.go b/packages/util/check-for-update.go
index d59b9528..6f693342 100644
--- a/packages/util/check-for-update.go
+++ b/packages/util/check-for-update.go
@@ -209,27 +209,88 @@ func writeUpdateCheckCache(cache *UpdateCheckCache) error {
return nil
}
-func DisplayAptInstallationChangeBanner(isSilent bool) {
- DisplayAptInstallationChangeBannerWithWriter(isSilent, os.Stderr)
+// migrationNoticeCacheTTL throttles the Cloudsmith migration notice so it shows
+// at most once per interval instead of on every command.
+const migrationNoticeCacheTTL = 24 * time.Hour
+
+// migrationGuideURL is where users are sent to repoint off Cloudsmith.
+// TODO: replace with the published migration guide URL before release.
+const migrationGuideURL = "https://infisical.com/docs/cli/usage"
+
+type migrationNoticeCache struct {
+ LastShownTime time.Time `json:"lastShownTime"`
}
-func DisplayAptInstallationChangeBannerWithWriter(isSilent bool, w io.Writer) {
- if isSilent {
+func getMigrationNoticeCachePath() (string, error) {
+ homeDir, err := GetHomeDir()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(homeDir, CONFIG_FOLDER_NAME, MIGRATION_NOTICE_CACHE_FILE_NAME), nil
+}
+
+// migrationNoticeRecentlyShown returns true if the notice was shown within the TTL.
+func migrationNoticeRecentlyShown() bool {
+ path, err := getMigrationNoticeCachePath()
+ if err != nil {
+ return false
+ }
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return false
+ }
+ var cache migrationNoticeCache
+ if err := json.Unmarshal(data, &cache); err != nil {
+ return false
+ }
+ return time.Since(cache.LastShownTime) < migrationNoticeCacheTTL
+}
+
+// recordMigrationNoticeShown stamps the cache so the notice is throttled.
+func recordMigrationNoticeShown() {
+ path, err := getMigrationNoticeCachePath()
+ if err != nil {
+ return
+ }
+ if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
+ return
+ }
+ data, err := json.Marshal(migrationNoticeCache{LastShownTime: time.Now()})
+ if err != nil {
return
}
+ // Best-effort write; a failure just means the notice may show again next run.
+ _ = os.WriteFile(path, data, 0600)
+}
- if runtime.GOOS == "linux" {
- _, err := exec.LookPath("apt-get")
- isApt := err == nil
- if isApt {
- yellow := color.New(color.FgYellow).SprintFunc()
- msg := fmt.Sprintf("%s",
- yellow("Update Required: Your current package installation script is outdated and will no longer receive updates.\nPlease update to the new installation script which can be found here https://infisical.com/docs/cli/overview#installation debian section\n"),
- )
+func DisplayPackageRepoMigrationNotice(isSilent bool) {
+ DisplayPackageRepoMigrationNoticeWithWriter(isSilent, os.Stderr)
+}
- fmt.Fprintln(w, msg)
- }
+// DisplayPackageRepoMigrationNoticeWithWriter prints a one-time-per-day notice
+// that the Linux package repository has moved off Cloudsmith. It shows on every
+// platform (so a macOS/Windows developer knows their Linux CI, containers, or
+// teammates need repointing), stays quiet in --silent mode, and can be disabled
+// with INFISICAL_DISABLE_MIGRATION_NOTICE.
+func DisplayPackageRepoMigrationNoticeWithWriter(isSilent bool, w io.Writer) {
+ if isSilent {
+ return
}
+ if os.Getenv("INFISICAL_DISABLE_MIGRATION_NOTICE") != "" {
+ return
+ }
+ if migrationNoticeRecentlyShown() {
+ return
+ }
+
+ yellow := color.New(color.FgYellow).SprintFunc()
+ fmt.Fprintln(w, yellow(
+ "Notice: the Infisical CLI Linux package repository has moved off Cloudsmith.\n"+
+ "If you installed the CLI on Linux via apt, yum/dnf, or apk, repoint your machine(s) to keep receiving updates: "+
+ migrationGuideURL+"\n",
+ ))
+
+ recordMigrationNoticeShown()
}
func getLatestTag(repoOwner string, repoName string) (string, time.Time, bool, error) {
diff --git a/packages/util/constants.go b/packages/util/constants.go
index 22bdf606..50300e85 100644
--- a/packages/util/constants.go
+++ b/packages/util/constants.go
@@ -69,7 +69,8 @@ const (
KUBERNETES_SERVICE_ACCOUNT_CA_CERT_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token"
- UPDATE_CHECK_CACHE_FILE_NAME = "update-check.json"
+ UPDATE_CHECK_CACHE_FILE_NAME = "update-check.json"
+ MIGRATION_NOTICE_CACHE_FILE_NAME = "migration-notice.json"
)
var (
diff --git a/packages/util/migration_notice_manual_test.go b/packages/util/migration_notice_manual_test.go
new file mode 100644
index 00000000..c1f41273
--- /dev/null
+++ b/packages/util/migration_notice_manual_test.go
@@ -0,0 +1,16 @@
+package util
+
+import (
+ "os"
+ "testing"
+)
+
+// Manual visual check of the Cloudsmith migration notice.
+// Run inside a Linux container with a package manager present:
+// go test ./packages/util/ -run TestMigrationNoticeManual -v
+func TestMigrationNoticeManual(t *testing.T) {
+ // Use an isolated HOME so the throttle cache starts empty and the notice prints.
+ tmp := t.TempDir()
+ t.Setenv("HOME", tmp)
+ DisplayPackageRepoMigrationNoticeWithWriter(false, os.Stdout)
+}
From 16be20d00faab76361fe0b1dff319bb2c9a95f72 Mon Sep 17 00:00:00 2001
From: Victor Hugo dos Santos
Date: Tue, 16 Jun 2026 14:09:08 -0300
Subject: [PATCH 2/3] Update README and CLI migration notice for Linux package
repository transition from Cloudsmith
- Revised README to clarify the migration of the Infisical CLI Linux package repository to `artifacts-cli.infisical.com`, including the shutdown date for Cloudsmith.
- Updated the migration guide URL in the code and added a new constant for the Cloudsmith sunset date.
- Enhanced the CLI migration notice to provide clearer instructions for users on repointing their machines and the implications of the migration.
---
README.md | 4 +++-
packages/util/check-for-update.go | 15 ++++++++++-----
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 42e366fe..ef33d992 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,9 @@
> [!IMPORTANT]
-> **The Linux package repository has moved off Cloudsmith.** We've migrated the Infisical CLI Linux package repository to our own host at `artifacts-cli.infisical.com`. Cloudsmith shuts down on **[DATE]**; after that, installs and updates from the old URL will fail. Every release, including older versions, is already on the new host. Reinstall the CLI by following the [migration steps]([MIGRATION_GUIDE_URL]) to repoint your machine.
+> **The Infisical CLI Linux package repository is moving off Cloudsmith.** To keep up with download volume, we're migrating the Linux package repository to our own host at `artifacts-cli.infisical.com`. Cloudsmith downloads will stop being served on **September 16th, 2026**, after which installs and updates from the old URL will fail.
+>
+> Every release, including all older versions, is already available on the new host. If you're on an existing setup, you don't need to change anything else, just repoint your machine to the new artifact URL by following the [migration steps](https://infisical.com/docs/cli/cloudsmith-migration).
## Introduction
diff --git a/packages/util/check-for-update.go b/packages/util/check-for-update.go
index 6f693342..ddb21e92 100644
--- a/packages/util/check-for-update.go
+++ b/packages/util/check-for-update.go
@@ -214,8 +214,10 @@ func writeUpdateCheckCache(cache *UpdateCheckCache) error {
const migrationNoticeCacheTTL = 24 * time.Hour
// migrationGuideURL is where users are sent to repoint off Cloudsmith.
-// TODO: replace with the published migration guide URL before release.
-const migrationGuideURL = "https://infisical.com/docs/cli/usage"
+const migrationGuideURL = "https://infisical.com/docs/cli/cloudsmith-migration"
+
+// migrationCloudsmithSunset is the date the old Cloudsmith repo stops serving.
+const migrationCloudsmithSunset = "September 16, 2026"
type migrationNoticeCache struct {
LastShownTime time.Time `json:"lastShownTime"`
@@ -284,10 +286,13 @@ func DisplayPackageRepoMigrationNoticeWithWriter(isSilent bool, w io.Writer) {
}
yellow := color.New(color.FgYellow).SprintFunc()
+ bold := color.New(color.FgYellow, color.Bold).SprintFunc()
+ fmt.Fprintln(w, bold("Important: the Infisical CLI Linux package repository is moving off Cloudsmith."))
fmt.Fprintln(w, yellow(
- "Notice: the Infisical CLI Linux package repository has moved off Cloudsmith.\n"+
- "If you installed the CLI on Linux via apt, yum/dnf, or apk, repoint your machine(s) to keep receiving updates: "+
- migrationGuideURL+"\n",
+ "What's happening: Cloudsmith stops serving on "+migrationCloudsmithSunset+". After that, installing or\n"+
+ "updating the CLI on Linux from the old Cloudsmith URL (apt, yum/dnf, apk) will fail.\n"+
+ "What to do: repoint your machine to the new host (artifacts-cli.infisical.com).\n"+
+ "Migration steps: "+migrationGuideURL+"\n",
))
recordMigrationNoticeShown()
From a309daf6111c7f693e2edc3513ec11ad7cefea9c Mon Sep 17 00:00:00 2001
From: Victor Hugo dos Santos
Date: Tue, 16 Jun 2026 17:32:07 -0300
Subject: [PATCH 3/3] Enhance APK sync integrity check in upload_to_s3.sh and
clean up migration notice code
- Added an integrity check in the APK upload script to ensure that the number of APKs in the staging area matches the count in S3 before proceeding with the sync, preventing incomplete uploads.
- Simplified comments in the migration notice display function to improve clarity and removed the manual test file for the migration notice.
---
packages/util/check-for-update.go | 10 ++--------
packages/util/migration_notice_manual_test.go | 16 ----------------
upload_to_s3.sh | 15 ++++++++++++++-
3 files changed, 16 insertions(+), 25 deletions(-)
delete mode 100644 packages/util/migration_notice_manual_test.go
diff --git a/packages/util/check-for-update.go b/packages/util/check-for-update.go
index ddb21e92..dc90713b 100644
--- a/packages/util/check-for-update.go
+++ b/packages/util/check-for-update.go
@@ -209,14 +209,10 @@ func writeUpdateCheckCache(cache *UpdateCheckCache) error {
return nil
}
-// migrationNoticeCacheTTL throttles the Cloudsmith migration notice so it shows
-// at most once per interval instead of on every command.
const migrationNoticeCacheTTL = 24 * time.Hour
-// migrationGuideURL is where users are sent to repoint off Cloudsmith.
const migrationGuideURL = "https://infisical.com/docs/cli/cloudsmith-migration"
-// migrationCloudsmithSunset is the date the old Cloudsmith repo stops serving.
const migrationCloudsmithSunset = "September 16, 2026"
type migrationNoticeCache struct {
@@ -270,10 +266,8 @@ func DisplayPackageRepoMigrationNotice(isSilent bool) {
}
// DisplayPackageRepoMigrationNoticeWithWriter prints a one-time-per-day notice
-// that the Linux package repository has moved off Cloudsmith. It shows on every
-// platform (so a macOS/Windows developer knows their Linux CI, containers, or
-// teammates need repointing), stays quiet in --silent mode, and can be disabled
-// with INFISICAL_DISABLE_MIGRATION_NOTICE.
+// that the Linux package repository has moved off Cloudsmith.
+// Stays quiet in --silent mode, and can be disabled with INFISICAL_DISABLE_MIGRATION_NOTICE.
func DisplayPackageRepoMigrationNoticeWithWriter(isSilent bool, w io.Writer) {
if isSilent {
return
diff --git a/packages/util/migration_notice_manual_test.go b/packages/util/migration_notice_manual_test.go
deleted file mode 100644
index c1f41273..00000000
--- a/packages/util/migration_notice_manual_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package util
-
-import (
- "os"
- "testing"
-)
-
-// Manual visual check of the Cloudsmith migration notice.
-// Run inside a Linux container with a package manager present:
-// go test ./packages/util/ -run TestMigrationNoticeManual -v
-func TestMigrationNoticeManual(t *testing.T) {
- // Use an isolated HOME so the throttle cache starts empty and the notice prints.
- tmp := t.TempDir()
- t.Setenv("HOME", tmp)
- DisplayPackageRepoMigrationNoticeWithWriter(false, os.Stdout)
-}
diff --git a/upload_to_s3.sh b/upload_to_s3.sh
index 0766c1fe..8e1653db 100755
--- a/upload_to_s3.sh
+++ b/upload_to_s3.sh
@@ -65,7 +65,20 @@ if ls *.apk 1> /dev/null 2>&1; then
# Sync existing packages from S3 (to preserve old versions)
echo "Syncing existing APK packages from S3..."
aws s3 sync "s3://$INFISICAL_CLI_S3_BUCKET/apk/" apk-staging/ --exclude "*/APKINDEX.tar.gz"
-
+
+ # Integrity gate: the APKINDEX is rebuilt from staging, so a partial sync would
+ # silently drop versions. Staging = synced repo + new apks, so it must have at
+ # least as many .apk as S3 per arch; if fewer, the sync was incomplete, so abort.
+ for arch in x86_64 aarch64; do
+ s3_count=$(aws s3 ls "s3://$INFISICAL_CLI_S3_BUCKET/apk/stable/main/$arch/" 2>/dev/null | grep -c '\.apk$' || true)
+ local_count=$(ls apk-staging/stable/main/$arch/*.apk 2>/dev/null | wc -l | tr -d ' ')
+ if [ "$local_count" -lt "$s3_count" ]; then
+ echo "Error: APK sync incomplete for $arch (S3 has $s3_count, staged $local_count). Aborting to avoid publishing a stale APKINDEX."
+ exit 1
+ fi
+ echo "APK integrity OK for $arch: staged $local_count >= S3 $s3_count"
+ done
+
# Validate APK private key exists
if [ ! -f "$APK_PRIVATE_KEY_PATH" ]; then
echo "Error: APK private key not found at $APK_PRIVATE_KEY_PATH"