From e02fe091489399f87e8bcfd68eb3015ecc89a30e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Mon, 4 May 2026 17:33:17 +0200 Subject: [PATCH 1/4] impr: check that docker bin exists --- internal/config/load.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/config/load.go b/internal/config/load.go index e37fb07..3be3d30 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "os" + "os/exec" "path/filepath" "strings" @@ -176,8 +177,16 @@ func readConfig(path string) (*Config, error) { } if cfg.Docker == nil { cfg.Docker = &Docker{Bin: "docker"} - } else if cfg.Docker.Bin == "" { - cfg.Docker.Bin = "docker" + } else { + if cfg.Docker.Bin == "" { + cfg.Docker.Bin = "docker" + } else { + bin := cfg.Docker.Bin + _, err := exec.LookPath(bin) + if err != nil { + return nil, err + } + } } return cfg, err From 0c1b2aeea244a10b4d39f1ed9b6700f37ede3144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Sun, 19 Apr 2026 14:03:14 +0200 Subject: [PATCH 2/4] feat: add config for tmp directory --- internal/config/config.go | 1 + internal/config/load.go | 6 ++++++ internal/config/load_test.go | 17 +++++++++++++++++ internal/config/testdata/docker.json | 10 ++++++++++ internal/engine/docker.go | 5 +++-- internal/fileio/fileio.go | 4 ++-- internal/fileio/fileio_test.go | 4 ++-- 7 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 internal/config/testdata/docker.json diff --git a/internal/config/config.go b/internal/config/config.go index e19c308..1b40eb7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -120,6 +120,7 @@ type HTTP struct { // A Docker describes Docker engine settings. type Docker struct { Bin string `json:"bin"` + Tmp string `json:"tmp"` } // setBoxDefaults sets default box properties diff --git a/internal/config/load.go b/internal/config/load.go index 3be3d30..54b7bfe 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -187,6 +187,9 @@ func readConfig(path string) (*Config, error) { return nil, err } } + if cfg.Docker.Tmp == "" { + cfg.Docker.Tmp = tempDir() + } } return cfg, err @@ -241,6 +244,9 @@ func readBoxesFile(path string) (map[string]*Box, error) { return boxes, err } +// tempDir returns the directory to use for temporary files. +var tempDir = os.TempDir + // readCommands reads command configs from a set of JSON files in the given path. func readCommands(cfg *Config, path string, pattern string) (*Config, error) { logx.Debug("reading commands from %s/%s", path, pattern) diff --git a/internal/config/load_test.go b/internal/config/load_test.go index cf5da5b..4dcd14a 100644 --- a/internal/config/load_test.go +++ b/internal/config/load_test.go @@ -1,6 +1,7 @@ package config import ( + "path/filepath" "testing" "github.com/nalgeon/be" @@ -8,12 +9,17 @@ import ( func TestRead(t *testing.T) { cfg, err := Read("testdata") + be.Err(t, err, nil) be.Equal(t, cfg.PoolSize, 8) be.Equal(t, cfg.Verbose, true) be.Equal(t, cfg.Box.Memory, 64) be.Equal(t, cfg.Step.User, "sandbox") + // docker + be.Equal(t, cfg.Docker.Bin, "docker") + be.Equal(t, cfg.Docker.Tmp, "") + // alpine box be.True(t, cfg.Boxes["custom-alpine"] != nil) be.Equal(t, cfg.Boxes["custom-alpine"].Image, "custom/alpine") @@ -23,3 +29,14 @@ func TestRead(t *testing.T) { be.True(t, cfg.Commands["python"] != nil) be.True(t, cfg.Commands["python"]["run"] != nil) } + +func TestReadDocker(t *testing.T) { + temp := filepath.Join(t.TempDir(), "TEMP") + tempDir = func() string { return temp } + + cfg, err := readConfig(filepath.Join("testdata", "docker.json")) + + be.Err(t, err, nil) + be.Equal(t, cfg.Docker.Bin, "docker") + be.Equal(t, cfg.Docker.Tmp, temp) +} diff --git a/internal/config/testdata/docker.json b/internal/config/testdata/docker.json new file mode 100644 index 0000000..715c7d9 --- /dev/null +++ b/internal/config/testdata/docker.json @@ -0,0 +1,10 @@ +{ + "box": { + }, + "step": { + }, + "docker": { + "bin": "", + "tmp": "" + } +} diff --git a/internal/engine/docker.go b/internal/engine/docker.go index 21b78c8..cef0314 100644 --- a/internal/engine/docker.go +++ b/internal/engine/docker.go @@ -32,18 +32,19 @@ type Docker struct { cfg *config.Config cmd *config.Command exe string // docker + tmp string // TMPDIR } // NewDocker creates a new Docker engine for a specific command. func NewDocker(cfg *config.Config, sandbox, command string) Engine { cmd := cfg.Commands[sandbox][command] - return &Docker{cfg, cmd, cfg.Docker.Bin} + return &Docker{cfg, cmd, cfg.Docker.Bin, cfg.Docker.Tmp} } // Exec executes the command and returns the output. func (e *Docker) Exec(req Request) Execution { // all steps operate in the same temp directory - dir, err := fileio.MkdirTemp(0777) + dir, err := fileio.MkdirTemp(e.tmp, 0777) if err != nil { err = NewExecutionError("create temp dir", err) return Fail(req.ID, err) diff --git a/internal/fileio/fileio.go b/internal/fileio/fileio.go index 6a9853a..9f5ce82 100644 --- a/internal/fileio/fileio.go +++ b/internal/fileio/fileio.go @@ -141,8 +141,8 @@ func JoinDir(dir string, name string) (string, error) { // MkdirTemp creates a new temporary directory with given permissions // and returns the pathname of the new directory. -func MkdirTemp(perm fs.FileMode) (string, error) { - dir, err := os.MkdirTemp("", "") +func MkdirTemp(dir string, perm fs.FileMode) (string, error) { + dir, err := os.MkdirTemp(dir, "") if err != nil { return "", err } diff --git a/internal/fileio/fileio_test.go b/internal/fileio/fileio_test.go index ceec855..6eff372 100644 --- a/internal/fileio/fileio_test.go +++ b/internal/fileio/fileio_test.go @@ -281,7 +281,7 @@ func TestJoinDir(t *testing.T) { func TestMkdirTemp(t *testing.T) { t.Run("default permissions", func(t *testing.T) { const perm = 0755 - dir, err := MkdirTemp(perm) + dir, err := MkdirTemp("", perm) be.Err(t, err, nil) defer func() { _ = os.Remove(dir) }() @@ -292,7 +292,7 @@ func TestMkdirTemp(t *testing.T) { t.Run("non-default permissions", func(t *testing.T) { const perm = 0777 - dir, err := MkdirTemp(perm) + dir, err := MkdirTemp(t.TempDir(), perm) be.Err(t, err, nil) defer func() { _ = os.Remove(dir) }() From 89b955b9ff0fabb41421452e6216d9fbfde0f028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Sun, 19 Apr 2026 17:40:21 +0200 Subject: [PATCH 3/4] feat: allow using tilde in tmp dir --- internal/config/load.go | 22 ++++++++++++++++++++++ internal/config/load_test.go | 10 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/internal/config/load.go b/internal/config/load.go index 54b7bfe..ab8ba4f 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -189,6 +189,13 @@ func readConfig(path string) (*Config, error) { } if cfg.Docker.Tmp == "" { cfg.Docker.Tmp = tempDir() + } else { + tmp := cfg.Docker.Tmp + dir, err := expandTilde(tmp) + if err != nil { + return nil, err + } + cfg.Docker.Tmp = dir } } @@ -247,6 +254,21 @@ func readBoxesFile(path string) (map[string]*Box, error) { // tempDir returns the directory to use for temporary files. var tempDir = os.TempDir +// userHomeDir returns the current user's home directory. +var userHomeDir = os.UserHomeDir + +// expandTilde expands strings like "~/.local" with the path. +func expandTilde(dir string) (string, error) { + if strings.HasPrefix(dir, "~/") { + homeDir, err := userHomeDir() + if err != nil { + return "", err + } + dir = strings.Replace(dir, "~", homeDir, 1) + } + return dir, nil +} + // readCommands reads command configs from a set of JSON files in the given path. func readCommands(cfg *Config, path string, pattern string) (*Config, error) { logx.Debug("reading commands from %s/%s", path, pattern) diff --git a/internal/config/load_test.go b/internal/config/load_test.go index 4dcd14a..509d2e6 100644 --- a/internal/config/load_test.go +++ b/internal/config/load_test.go @@ -40,3 +40,13 @@ func TestReadDocker(t *testing.T) { be.Equal(t, cfg.Docker.Bin, "docker") be.Equal(t, cfg.Docker.Tmp, temp) } + +func TestExpandTilde(t *testing.T) { + home := filepath.Join(t.TempDir(), "HOME") + userHomeDir = func() (string, error) { return home, nil } + + tmp, err := expandTilde("~/tmp") + be.Err(t, err, nil) + + be.Equal(t, tmp, home+"/tmp") +} From 0c8c0f9e0ddc7066a4e9b6c9582c008b55cc8151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Mon, 4 May 2026 17:33:24 +0200 Subject: [PATCH 4/4] impr: create the docker tmp dir --- internal/config/load.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/config/load.go b/internal/config/load.go index ab8ba4f..d419b9c 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -195,6 +195,10 @@ func readConfig(path string) (*Config, error) { if err != nil { return nil, err } + err = os.MkdirAll(dir, 0o755) + if err != nil { + return nil, err + } cfg.Docker.Tmp = dir } }