Skip to content

Commit a4dff66

Browse files
committed
[Bug fix] Ensure bin-wrappers use latest devbox binary to prevent false update notifications
1 parent 80c12e0 commit a4dff66

File tree

7 files changed

+85
-17
lines changed

7 files changed

+85
-17
lines changed

internal/boxcli/version.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ import (
99
"runtime"
1010

1111
"github.com/spf13/cobra"
12+
"go.jetpack.io/devbox/internal/wrapnix"
1213

1314
"go.jetpack.io/devbox/internal/build"
1415
"go.jetpack.io/devbox/internal/envir"
1516
"go.jetpack.io/devbox/internal/vercheck"
1617
)
1718

1819
type versionFlags struct {
19-
verbose bool
20+
verbose bool
21+
updateDevboxSymlink bool
2022
}
2123

2224
func versionCmd() *cobra.Command {
@@ -33,6 +35,11 @@ func versionCmd() *cobra.Command {
3335
command.Flags().BoolVarP(&flags.verbose, "verbose", "v", false, // value
3436
"displays additional version information",
3537
)
38+
command.Flags().BoolVarP(&flags.updateDevboxSymlink, "update-devbox-symlink", "u", false, // value
39+
"update the devbox symlink to point to the current version",
40+
)
41+
_ = command.Flags().MarkHidden("update-devbox-symlink")
42+
3643
command.AddCommand(selfUpdateCmd())
3744
return command
3845
}
@@ -60,6 +67,14 @@ func versionCmdFunc(cmd *cobra.Command, _ []string, flags versionFlags) error {
6067
fmt.Fprintf(w, "Commit Time: %v\n", v.CommitDate)
6168
fmt.Fprintf(w, "Go Version: %v\n", v.GoVersion)
6269
fmt.Fprintf(w, "Launcher: %v\n", v.LauncherVersion)
70+
71+
// TODO: in a subsequent PR, we should do this when flags.updateDevboxSymlink is true.
72+
// Not doing for now, since users who have Devbox binary prior to this edit
73+
// (before Devbox v0.5.9) will not invoke this flag in `devbox version update`.
74+
// But we still want this to run for them.
75+
if _, err := wrapnix.CreateDevboxSymlink(); err != nil {
76+
return err
77+
}
6378
} else {
6479
fmt.Fprintf(w, "%v\n", v.Version)
6580
}

internal/cloud/openssh/config.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"text/template"
1616

1717
"github.com/pkg/errors"
18+
"go.jetpack.io/devbox/internal/fileutil"
1819
)
1920

2021
// These must match what's in sshConfigTmpl. We should eventually make the hosts
@@ -211,17 +212,8 @@ func containsDevboxInclude(r io.Reader) bool {
211212
return false
212213
}
213214

214-
// move to a file utility
215215
func EnsureDirExists(path string, perm fs.FileMode, chmod bool) error {
216-
if err := os.Mkdir(path, perm); err != nil && !errors.Is(err, fs.ErrExist) {
217-
return errors.WithStack(err)
218-
}
219-
if chmod {
220-
if err := os.Chmod(path, perm); err != nil {
221-
return errors.WithStack(err)
222-
}
223-
}
224-
return nil
216+
return fileutil.EnsureDirExists(path, perm, chmod)
225217
}
226218

227219
// returns path to ~/.config/devbox/ssh

internal/fileutil/fileutil.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
package fileutil
55

66
import (
7+
"io/fs"
78
"os"
89
"strings"
10+
11+
"github.com/pkg/errors"
912
)
1013

1114
// TODO: publish as it's own shared package that other binaries can use.
@@ -55,3 +58,15 @@ func FileContains(path string, substring string) (bool, error) {
5558
}
5659
return strings.Contains(string(data), substring), nil
5760
}
61+
62+
func EnsureDirExists(path string, perm fs.FileMode, chmod bool) error {
63+
if err := os.MkdirAll(path, perm); err != nil && !errors.Is(err, fs.ErrExist) {
64+
return errors.WithStack(err)
65+
}
66+
if chmod {
67+
if err := os.Chmod(path, perm); err != nil {
68+
return errors.WithStack(err)
69+
}
70+
}
71+
return nil
72+
}

internal/impl/packages.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ func (d *Devbox) ensurePackagesAreInstalled(ctx context.Context, mode installMod
215215
// Ensure we clean out packages that are no longer needed.
216216
d.lockfile.Tidy()
217217

218+
if err := wrapnix.CreateWrappers(ctx, d); err != nil {
219+
return err
220+
}
221+
218222
return d.lockfile.Save()
219223
}
220224

internal/vercheck/vercheck.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ func triggerUpdate(stdErr io.Writer) (*updatedVersions, error) {
175175
}
176176

177177
// TODO savil. Add a --json flag to devbox version and parse the output as JSON
178-
cmd := exec.Command(exePath, "version", "-v")
178+
cmd := exec.Command(exePath, "version", "-v", "--update-devbox-symlink")
179179

180180
buf := new(bytes.Buffer)
181181
cmd.Stdout = io.MultiWriter(stdErr, buf)

internal/wrapnix/wrapper.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import (
1414

1515
"github.com/pkg/errors"
1616
"go.jetpack.io/devbox/internal/cmdutil"
17+
"go.jetpack.io/devbox/internal/fileutil"
1718
"go.jetpack.io/devbox/internal/nix"
1819
"go.jetpack.io/devbox/internal/plugin"
20+
"go.jetpack.io/devbox/internal/xdg"
1921
)
2022

2123
type devboxer interface {
@@ -51,7 +53,7 @@ func CreateWrappers(ctx context.Context, devbox devboxer) error {
5153
}
5254
// get absolute path of devbox binary that the launcher script invokes
5355
// to avoid causing an infinite loop when coreutils gets installed
54-
executablePath, err := os.Executable()
56+
devboxSymlinkPath, err := CreateDevboxSymlink()
5557
if err != nil {
5658
return err
5759
}
@@ -62,7 +64,7 @@ func CreateWrappers(ctx context.Context, devbox devboxer) error {
6264
BashPath: bashPath,
6365
Command: bin,
6466
ShellEnvHash: shellEnvHash,
65-
DevboxBinaryPath: executablePath,
67+
DevboxSymlinkDir: filepath.Dir(devboxSymlinkPath),
6668
destPath: filepath.Join(destPath, filepath.Base(bin)),
6769
}); err != nil {
6870
return errors.WithStack(err)
@@ -72,12 +74,50 @@ func CreateWrappers(ctx context.Context, devbox devboxer) error {
7274
return createSymlinksForSupportDirs(devbox.ProjectDir())
7375
}
7476

77+
// CreateDevboxSymlink creates a symlink to the devbox binary
78+
//
79+
// Needed because:
80+
//
81+
// 1. The bin-wrappers cannot invoke the devbox via the Launcher. The Launcher script
82+
// invokes some coreutils commands that may themselves be installed by devbox
83+
// and so be bin-wrappers. This causes an infinite loop.
84+
// So, the bin-wrappers need to directly invoke the devbox binary.
85+
//
86+
// 2. The devbox binary's path will change when devbox is updated. This means
87+
// that bin-wrappers may invoke older devbox binaries, which have different
88+
// functionality. Annoyingly, they will also print "New devbox available" messages,
89+
// even though the user has just updated devbox.
90+
// So, the bin-wrappers need to use a symlink to the latest devbox binary. This
91+
// symlink is updated when devbox is updated.
92+
func CreateDevboxSymlink() (string, error) {
93+
curDir := xdg.CacheSubpath(filepath.Join("devbox", "current"))
94+
if err := fileutil.EnsureDirExists(curDir, 0755, false /*chmod*/); err != nil {
95+
return "", err
96+
}
97+
currentDevboxSymlinkPath := filepath.Join(curDir, "devbox")
98+
99+
devboxBinaryPath, err := os.Executable()
100+
if err != nil {
101+
return "", errors.WithStack(err)
102+
}
103+
104+
// We will always re-create this symlink to ensure correctness.
105+
if err := os.Remove(currentDevboxSymlinkPath); err != nil && !errors.Is(err, os.ErrNotExist) {
106+
return "", errors.WithStack(err)
107+
}
108+
109+
if err := os.Symlink(devboxBinaryPath, currentDevboxSymlinkPath); err != nil {
110+
return "", errors.WithStack(err)
111+
}
112+
return currentDevboxSymlinkPath, nil
113+
}
114+
75115
type createWrapperArgs struct {
76116
devboxer
77117
BashPath string
78118
Command string
79119
ShellEnvHash string
80-
DevboxBinaryPath string
120+
DevboxSymlinkDir string
81121
destPath string
82122
}
83123

internal/wrapnix/wrapper.sh.tmpl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!{{ .BashPath }}
22

3+
export PATH="{{ .DevboxSymlinkDir }}:$PATH"
4+
35
{{/*
46
# If env variable has never been set by devbox we set it, but also
57
# default to env value set by user. This means plugin env variables behave a bit
@@ -19,7 +21,7 @@ DO_NOT_TRACK=1 can be removed once we optimize segment to queue events.
1921

2022
if [[ "${{ .ShellEnvHashKey }}" != "{{ .ShellEnvHash }}" ]] && [[ -z "${{ .ShellEnvHashKey }}_GUARD" ]]; then
2123
export {{ .ShellEnvHashKey }}_GUARD=true
22-
eval "$(DO_NOT_TRACK=1 {{ .DevboxBinaryPath }} shellenv -c {{ .ProjectDir }})"
24+
eval "$(DO_NOT_TRACK=1 devbox shellenv -c {{ .ProjectDir }})"
2325
fi
2426

2527
{{/*
@@ -29,6 +31,6 @@ should be in PATH.
2931

3032
DO_NOT_TRACK=1 can be removed once we optimize segment to queue events.
3133
*/ -}}
32-
eval "$(DO_NOT_TRACK=1 {{ .DevboxBinaryPath }} shellenv only-path-without-wrappers)"
34+
eval "$(DO_NOT_TRACK=1 devbox shellenv only-path-without-wrappers)"
3335

3436
exec {{ .Command }} "$@"

0 commit comments

Comments
 (0)