diff --git a/docs/changelog.md b/docs/changelog.md index 7c0a9435..82712c22 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,17 @@ Lerd uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [1.30.1] - 2026-07-23 + +Two fixes: `lerd start` stopped prompting to reinstall the DNS sudoers rule on macOS every run, and the web UI's open-terminal button opens in the site directory again where ptyxis is the terminal. + +### Fixed + +- **macOS prompted to reinstall the DNS sudoers rule on every `lerd start`** (#1101). The passwordless-liveness probe that decides whether the drop-in is still active was hardcoded to `resolvectl`, a systemd tool that exists only on Linux, so on macOS `sudo -n` always refused with "a password is required", which the probe read as a conclusive "the grant is gone" and rewrote the rule, prompting for sudo, even when the installed `/etc/sudoers.d/lerd` was byte identical to its recorded hash. The probe command is platform specific now: Linux keeps `resolvectl --version`, macOS exercises `mkdir -p /etc/resolver`, a command the drop-in already grants verbatim and which is idempotent, so it succeeds silently when the grant is live and only reports it gone when sudo genuinely refuses. +- **The open-terminal button opened in the home directory on ptyxis** (#1103). The web UI launched ptyxis with a bare `--working-directory`, but ptyxis is single-instance and applies that flag only alongside `--new-window`, `--tab` or `-x`, so it was ignored and a new window opened in the home directory instead of the site. The button passes `--new-window` now, so the terminal opens in the site or worktree path. Every other emulator was already correct and is unchanged. + +--- + ## [1.30.0] - 2026-07-22 The 1.30.0 line shrinks what a PHP version owns and widens what the dashboard can reach. Custom extensions and packages became one declared set applied to every image lerd builds, and php.ini follows with a shared scope that fills the gaps under each version's own file, so a site that changes version stops silently losing what the developer asked for. The dashboard manages an engine's databases from its service page with snapshots, export and import, offers to install a service the project asks for but never had, keeps every notification in a bell that survives a reload, renders log output in colour and hands any log to a terminal, and groups the services list by type. `lerd doctor` stops merely reporting and learns to repair what it safely can, notifications grow a native desktop sink alongside the new Lerd desktop app, and the terminal dashboard moves onto the Charm v2 stack. Git worktrees get a round of their own, resolving their own PHP version, their own image, their own pin and their own database from wherever a command is run. Underneath all of it there is a long tail of resolution and resource work: `.test` keeps answering with no network at all and comes back after a sleep, nginx stops rejecting a config over a directive lerd also declares, and the daemon stops paying for background work while nobody is looking at it. diff --git a/internal/ui/server.go b/internal/ui/server.go index 28572f6b..81b82d57 100644 --- a/internal/ui/server.go +++ b/internal/ui/server.go @@ -499,33 +499,36 @@ func graphicalEnv() []string { return out } -// openTerminalAt opens the user's preferred terminal emulator in dir. -// It checks $TERMINAL first, then falls back to a list of common emulators. -func openTerminalAt(dir string) error { - type termCmd struct { - bin string - args []string - } +// terminalCmd is a terminal emulator binary and the args that open it at a dir. +type terminalCmd struct { + bin string + args []string +} - candidates := []termCmd{} +// terminalDirCandidates returns the ordered emulator candidates for opening a +// new terminal at dir. $TERMINAL leads, then a fixed fallback list. +func terminalDirCandidates(dir string) []terminalCmd { + candidates := []terminalCmd{} if t := os.Getenv("TERMINAL"); t != "" { - candidates = append(candidates, termCmd{t, []string{}}) + candidates = append(candidates, terminalCmd{t, []string{}}) } candidates = append(candidates, - termCmd{"kitty", []string{"--directory", dir}}, - termCmd{"foot", []string{"--working-directory", dir}}, - termCmd{"alacritty", []string{"--working-directory", dir}}, - termCmd{"wezterm", []string{"start", "--cwd", dir}}, - termCmd{"ghostty", []string{"--working-directory=" + dir}}, - termCmd{"ptyxis", []string{"--working-directory", dir}}, - termCmd{"konsole", []string{"--separate", "--workdir", dir}}, - termCmd{"gnome-terminal", []string{"--working-directory", dir}}, - termCmd{"xfce4-terminal", []string{"--working-directory", dir}}, - termCmd{"tilix", []string{"--working-directory", dir}}, - termCmd{"terminator", []string{"--working-directory", dir}}, - termCmd{"xterm", []string{"-e", "sh", "-c", `cd "$0" && exec "$SHELL"`, dir}}, + terminalCmd{"kitty", []string{"--directory", dir}}, + terminalCmd{"foot", []string{"--working-directory", dir}}, + terminalCmd{"alacritty", []string{"--working-directory", dir}}, + terminalCmd{"wezterm", []string{"start", "--cwd", dir}}, + terminalCmd{"ghostty", []string{"--working-directory=" + dir}}, + // ptyxis is single-instance: --working-directory is honoured only + // alongside --new-window/--tab/-x, so a bare launch lands in $HOME. + terminalCmd{"ptyxis", []string{"--new-window", "--working-directory", dir}}, + terminalCmd{"konsole", []string{"--separate", "--workdir", dir}}, + terminalCmd{"gnome-terminal", []string{"--working-directory", dir}}, + terminalCmd{"xfce4-terminal", []string{"--working-directory", dir}}, + terminalCmd{"tilix", []string{"--working-directory", dir}}, + terminalCmd{"terminator", []string{"--working-directory", dir}}, + terminalCmd{"xterm", []string{"-e", "sh", "-c", `cd "$0" && exec "$SHELL"`, dir}}, ) if runtime.GOOS == "darwin" { @@ -533,12 +536,18 @@ func openTerminalAt(dir string) error { // command — cleaner than `do script "cd ... && exec $SHELL"` which types // the command visibly into the shell. iTerm2 supports the same via open. if _, err := os.Stat("/Applications/iTerm.app"); err == nil { - candidates = append(candidates, termCmd{"open", []string{"-a", "iTerm", dir}}) + candidates = append(candidates, terminalCmd{"open", []string{"-a", "iTerm", dir}}) } - candidates = append(candidates, termCmd{"open", []string{"-a", "Terminal", dir}}) + candidates = append(candidates, terminalCmd{"open", []string{"-a", "Terminal", dir}}) } - for _, t := range candidates { + return candidates +} + +// openTerminalAt opens the user's preferred terminal emulator in dir. +// It checks $TERMINAL first, then falls back to a list of common emulators. +func openTerminalAt(dir string) error { + for _, t := range terminalDirCandidates(dir) { bin, err := exec.LookPath(t.bin) if err != nil { continue diff --git a/internal/ui/terminal_env_test.go b/internal/ui/terminal_env_test.go index 69a8893e..d702dfc7 100644 --- a/internal/ui/terminal_env_test.go +++ b/internal/ui/terminal_env_test.go @@ -47,6 +47,30 @@ func TestGraphicalEnvPreservesBaseEnvAndPatchesDisplay(t *testing.T) { } } +func TestTerminalDirCandidatesOpenPtyxisInDir(t *testing.T) { + t.Setenv("TERMINAL", "") + const dir = "/home/user/project" + var ptyxis *terminalCmd + for _, c := range terminalDirCandidates(dir) { + if c.bin == "ptyxis" { + cp := c + ptyxis = &cp + } + } + if ptyxis == nil { + t.Fatal("ptyxis is not among the terminal candidates") + } + joined := strings.Join(ptyxis.args, " ") + // ptyxis is single-instance: without --new-window (or --tab/-x) it ignores + // --working-directory and opens a new window in $HOME instead of the site. + if !strings.Contains(joined, "--new-window") { + t.Errorf("ptyxis args %v lack --new-window, so %q would be ignored", ptyxis.args, dir) + } + if !strings.Contains(joined, dir) { + t.Errorf("ptyxis args %v do not carry the target dir %q", ptyxis.args, dir) + } +} + func TestGraphicalEnvDoesNotDuplicateKeys(t *testing.T) { t.Setenv("XDG_SESSION_TYPE", "wayland") env := graphicalEnv() diff --git a/internal/version/version.go b/internal/version/version.go index 5a359756..40208d44 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -9,7 +9,7 @@ import "fmt" // -X github.com/geodro/lerd/internal/version.Commit= // -X github.com/geodro/lerd/internal/version.Date= var ( - Version = "1.30.0" + Version = "1.30.1" Commit = "none" Date = "unknown" )