Skip to content

feat: add built-in ipfs update command#11203

Merged
lidel merged 18 commits intomasterfrom
feat/builtin-update-command
Apr 10, 2026
Merged

feat: add built-in ipfs update command#11203
lidel merged 18 commits intomasterfrom
feat/builtin-update-command

Conversation

@lidel
Copy link
Copy Markdown
Member

@lidel lidel commented Feb 16, 2026

Warning

Not finished yet, parking here until we work on 0.41

adds ipfs update command tree that downloads pre-built Kubo binaries from GitHub Releases, verifies SHA-512 checksums, and replaces the running binary in place.

subcommands:

  • ipfs update check -- query GitHub for newer versions
  • ipfs update versions -- list available releases
  • ipfs update install [version] -- download, verify, backup, and atomically replace the current binary
  • ipfs update revert -- restore the previously backed up binary from $IPFS_PATH/old-bin/

read-only subcommands (check, versions) work while the daemon is running. install and revert require the daemon to be stopped first.

design decisions:

  • uses GitHub Releases API instead of dist.ipfs.tech because GitHub is harder to censor in regions that block IPFS infrastructure
  • honors GITHUB_TOKEN/GH_TOKEN to avoid unauthenticated rate limits
  • backs up the current binary before replacing, with permission-error fallback that saves to a temp dir with manual sudo mv instructions
  • KUBO_UPDATE_GITHUB_URL env var redirects API calls for integration testing; IPFS_VERSION_FAKE overrides the reported version
  • unit tests use mock HTTP servers and the var override; CLI tests use the env vars with a temp binary copy so the real build is never touched

References

adds `ipfs update` command tree that downloads pre-built Kubo binaries
from GitHub Releases, verifies SHA-512 checksums, and replaces the
running binary in place.

subcommands:

- `ipfs update check` -- query GitHub for newer versions
- `ipfs update versions` -- list available releases
- `ipfs update install [version]` -- download, verify, backup, and
  atomically replace the current binary
- `ipfs update revert` -- restore the previously backed up binary
  from `$IPFS_PATH/old-bin/`

read-only subcommands (check, versions) work while the daemon is
running. install and revert require the daemon to be stopped first.

design decisions:

- uses GitHub Releases API instead of dist.ipfs.tech because GitHub
  is harder to censor in regions that block IPFS infrastructure
- honors GITHUB_TOKEN/GH_TOKEN to avoid unauthenticated rate limits
- backs up the current binary before replacing, with permission-error
  fallback that saves to a temp dir with manual `sudo mv` instructions
- `KUBO_UPDATE_GITHUB_URL` env var redirects API calls for integration
  testing; `IPFS_VERSION_FAKE` overrides the reported version
- unit tests use mock HTTP servers and the var override; CLI tests use
  the env vars with a temp binary copy so the real build is never
  touched

resolves #10937
Resolve changelog conflict by placing the update command entry
after the cid inspect section.
- cap decompressed binary at 1 GB to block zip/tar bombs
- propagate tar.gz/zip errors instead of swallowing them
- fall back to 1h context timeout when --timeout is not set
- warn on stderr when daemon lock check fails
- clarify that fetch+verify+extract complete before touching binary
The test harness hardcodes the binary path as `cmd/ipfs/ipfs`
without the `.exe` suffix. On Windows the built binary is
`ipfs.exe`, so copyBuiltBinary needs to append the extension.
On Windows the OS locks the running executable, so atomicfile cannot
rename over it. The install command falls back to saving the new binary
to a temp path. Accept both outcomes in TestUpdateInstall: in-place
replacement (Unix) or permission-denied fallback (Windows). Also fix
stash path to include .exe suffix on Windows.

- test/cli/update_test.go: branch on runtime.GOOS for install assertions
- test/sharness/t0063-external.sh: remove, tested the old ExternalBinary
  delegation which is replaced by the built-in update command
- .github/workflows/test-migrations.yml: pass GITHUB_TOKEN to avoid rate limits
@lidel lidel force-pushed the feat/builtin-update-command branch from da9c63c to cd16d29 Compare April 9, 2026 00:31
On Windows, Process.Wait() sets the handle state to "released" rather
than "done", so a subsequent Signal() returns syscall.EINVAL instead
of os.ErrProcessDone. This caused StopDaemon cleanup to panic on
Windows CI. Treat both errors as "process already exited".
@lidel lidel force-pushed the feat/builtin-update-command branch from cd16d29 to 6fb1eb6 Compare April 9, 2026 01:47
@lidel lidel marked this pull request as ready for review April 9, 2026 02:04
@lidel lidel requested a review from a team as a code owner April 9, 2026 02:04
@lidel lidel mentioned this pull request Apr 9, 2026
30 tasks
Copy link
Copy Markdown
Contributor

@guillaumemichel guillaumemichel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice feature!

Maybe add command to purge stashed versions, or document they can/should be purged manually when not needed anymore to prevent them from piling up?

Comment thread core/commands/update.go Outdated
lidel added 12 commits April 10, 2026 03:23
Drops every backed-up Kubo binary from $IPFS_PATH/old-bin/ so users can
reclaim disk space without hand-deleting files. Safe with the daemon
running, only touches the backup directory.

- update.go: extract stashDirName const, factor out listStashes()
  helper, add updateCleanCmd
- commands_test.go: register /update/clean
- test/cli/update_test.go: TestUpdateClean covers removal, empty dir,
  json output, and preservation of unrelated files
Drop the marketing opener, the duplicate install example, and the
revert/versions sentence; all are covered by 'ipfs update --help'.
Mention the new 'clean' subcommand in the trailing pointer.
Both fuse_test.go and realworld_test.go rely on Unix-only APIs
(syscall.Truncate, POSIX tools). The sibling xattr_*_test.go files
were already gated, but these two compiled everywhere, so any workflow
running 'go test ./test/cli/...' on Windows hit 'undefined: syscall.Truncate'.

Use the same '(linux || darwin || freebsd) && !nofuse' constraint that
the fuse/ packages already use so platform gating is consistent.
TestUpdateInstall and TestUpdateRevert write a copy of the ipfs
binary and then exec it. When other tests run in parallel, a
concurrent fork() can inherit the still-open write fd into its
child, leaving the freshly written file 'text file busy' for exec
until the sibling child execs.

Dropping t.Parallel() on these two tests ensures no other goroutine
is mid-fork while the binary is being written, which is the only
reliable way to avoid the ETXTBSY race without clever fd tricks.
GitHub's macOS runners intermittently lose DNS for api.github.com,
which fails the real-network subtests in TestUpdate. Point the
resolver at 1.1.1.1 and 8.8.8.8 on every active network service
and flush the DNS cache before running the update tests.
The previous fallback wrote to a predictable path (/tmp/ipfs-<ver>),
which on shared systems lets a local attacker pre-create the path
as a symlink and steer the user's subsequent 'sudo mv' anywhere.
Switch to os.CreateTemp so the path is unique and exclusively owned
by this process.
IPFS_VERSION_FAKE and KUBO_UPDATE_GITHUB_URL are test-only escape
hatches with no production use case. The TEST_ prefix signals this
clearly and reduces the chance of accidental use in production.

- IPFS_VERSION_FAKE      -> TEST_KUBO_VERSION
- KUBO_UPDATE_GITHUB_URL -> TEST_KUBO_UPDATE_GITHUB_URL
silently skipping the daemon lock check on path-resolution failure can
mask a misconfigured IPFS_PATH; print a warning so the user notices
before the install proceeds.
Revert the Sync() addition in atomicfile.Close() to avoid widening
the failure surface for existing migration callers that panic on
Close errors (Must(out.Close()) in WithBackup). The stashBinary
fsync in update.go is kept since that code path is new.

- revert repo/fsrepo/migrations/atomicfile/atomicfile.go to master
- use errors.Is(err, io.EOF) in extractFromTarGz
@lidel lidel merged commit 1f54f1d into master Apr 10, 2026
20 checks passed
@lidel lidel deleted the feat/builtin-update-command branch April 10, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment