diff --git a/internal/apply/process.go b/internal/apply/process.go index f79fd87..a548d44 100644 --- a/internal/apply/process.go +++ b/internal/apply/process.go @@ -214,7 +214,7 @@ func unitFor(r *declaration.Process) string { if r.User != "" { fmt.Fprintf(&b, "User=%s\n", r.User) } - fmt.Fprintf(&b, "ExecStart=%s\n", strings.Join(r.Run, " ")) + fmt.Fprintf(&b, "ExecStart=%s\n", strings.Join(runFrom(r), " ")) if r.Schedule != "" { // Started by its timer and expected to finish. Restarting it would have it run // continuously between fires, which is the opposite of a schedule. @@ -346,3 +346,15 @@ func removeProcess(ctx context.Context, a store.Applied, run Runner) (string, st } return "removed", "stopped; its unit and its bundle removed — the mesh's own code", nil } + +// runFrom is the command as the unit runs it. **A command written `./name` is that file in the +// process's own unpacked bundle** (novox/hq ADR 0193): a bundle compiled to a binary runs itself, +// and only the host knows where it unpacked it, while the service manager takes an absolute path or +// a name it finds on its own search path — never one relative to the working directory. +func runFrom(r *declaration.Process) []string { + run := append([]string(nil), r.Run...) + if len(run) > 0 && strings.HasPrefix(run[0], "./") { + run[0] = filepath.Join(daemonRoot, r.Name, strings.TrimPrefix(run[0], "./")) + } + return run +} diff --git a/internal/apply/process_test.go b/internal/apply/process_test.go index 70b9bdb..21308d5 100644 --- a/internal/apply/process_test.go +++ b/internal/apply/process_test.go @@ -313,3 +313,16 @@ func TestAProcessAppliedAgainIsUnchangedAndNotRestarted(t *testing.T) { t.Errorf("third apply: %+v, want unchanged", o) } } + +// novox/hq ADR 0193: a bundle compiled to a binary runs itself — `./name` is that file in the +// process's own unpacked bundle, made absolute because the service manager takes nothing relative. +func TestAProcessRunsItsOwnBundlesBinary(t *testing.T) { + unit := unitFor(&declaration.Process{Name: "node-tools", Run: []string{"./node-tools", "serve"}}) + if !strings.Contains(unit, "ExecStart="+filepath.Join(daemonRoot, "node-tools", "node-tools")+" serve\n") { + t.Errorf("the binary is not run from its bundle:\n%s", unit) + } + other := unitFor(&declaration.Process{Name: "x", Run: []string{"node", "src/main.js"}}) + if !strings.Contains(other, "ExecStart=node src/main.js\n") { + t.Errorf("a command found on the path was changed:\n%s", other) + } +}