@@ -480,7 +480,7 @@ type nativeServiceUnitInput struct {
|
||||
}
|
||||
|
||||
const (
|
||||
systemdServicePath = "/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:%h/.local/bin:%h/.bun/bin:%h/.opencode/bin"
|
||||
systemdServicePath = "/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:%h/.local/bin:%h/.bun/bin:%h/.opencode/bin:%h/.npm-global/bin"
|
||||
launchdServicePath = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSystemdServicePathExecutesNPMGlobalCLI(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("systemd service PATH is POSIX-only")
|
||||
}
|
||||
home := t.TempDir()
|
||||
binDir := filepath.Join(home, ".npm-global", "bin")
|
||||
if err := os.MkdirAll(binDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const cli = "kandev-test-npm-global-agent"
|
||||
wantPath := filepath.Join(binDir, cli)
|
||||
if err := os.WriteFile(wantPath, []byte("#!/bin/sh\nprintf 'npm-global-agent-ok'\n"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
unit := renderSystemdUnit(nativeServiceUnitInput{
|
||||
Executable: "/opt/kandev/bin/kandev",
|
||||
HomeDir: filepath.Join(home, ".kandev"),
|
||||
})
|
||||
var servicePath string
|
||||
for _, line := range strings.Split(unit, "\n") {
|
||||
value, ok := strings.CutPrefix(line, "Environment=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
value = strings.Trim(value, `"`)
|
||||
value, ok = strings.CutPrefix(value, "PATH=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// %h is the systemd home-dir specifier; it differs from input.HomeDir (kandev data dir).
|
||||
servicePath = strings.ReplaceAll(value, "%h", home)
|
||||
break
|
||||
}
|
||||
if servicePath == "" {
|
||||
t.Fatal("generated unit has no PATH environment entry")
|
||||
}
|
||||
t.Setenv("PATH", servicePath)
|
||||
resolved, err := exec.LookPath(cli)
|
||||
if err != nil {
|
||||
t.Fatalf("global npm CLI is not discoverable with generated service PATH: %v", err)
|
||||
}
|
||||
if resolved != wantPath {
|
||||
t.Fatalf("resolved CLI = %q, want %q", resolved, wantPath)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
output, err := exec.CommandContext(ctx, cli).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("execute global npm CLI: %v: %s", err, output)
|
||||
}
|
||||
if string(output) != "npm-global-agent-ok" {
|
||||
t.Fatalf("CLI output = %q", output)
|
||||
}
|
||||
}
|
||||
@@ -340,3 +340,16 @@ Reinstall using the upgraded `kandev` binary, preserve the original `--system` a
|
||||
### Service starts but agents fail
|
||||
|
||||
Read service logs first. A service has a smaller `PATH` and no interactive shell environment, so tools or credentials visible in a terminal may be absent. Configure executor credentials through Kandev's profile/settings paths, use stable executable paths, and verify Docker/SSH/Sprites connectivity as described in [Executors](executors.md#troubleshooting).
|
||||
|
||||
Linux user services include `~/.npm-global/bin` in their generated `PATH` for
|
||||
agent CLIs installed with that npm prefix. After upgrading an existing user
|
||||
service, regenerate its unit and restart it to apply the updated `PATH`:
|
||||
|
||||
```bash
|
||||
# Repeat your original install-time flags when used.
|
||||
kandev service install
|
||||
kandev service restart
|
||||
```
|
||||
|
||||
Restarting alone does not update an older unit's `PATH`. Other custom npm
|
||||
prefixes are not automatically added to the service environment.
|
||||
|
||||
Reference in New Issue
Block a user