Skip to content

Commit 16cf08c

Browse files
committed
[FIX] Installer self update.
1 parent e57a88d commit 16cf08c

5 files changed

Lines changed: 109 additions & 33 deletions

File tree

cmd/evo/main.go

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"errors"
56
"flag"
67
"fmt"
78
"os"
@@ -226,10 +227,54 @@ func runTUI(ctx context.Context, mode ui.Mode, installOpt *installengine.Options
226227
defer cancel()
227228
engine.Run(engineCtx, events, actions)
228229

229-
if err := ui.RunWithCancel(ctx, mode, events, actions, ui.Meta{
230+
res, err := ui.RunWithCancel(ctx, mode, events, actions, ui.Meta{
230231
Version: Version,
231232
Tagline: "The world’s fastest CMS!",
232-
}, cancel); err != nil {
233+
}, cancel)
234+
if err != nil {
235+
fmt.Fprintln(os.Stderr, err)
236+
return 1
237+
}
238+
239+
if len(res.PostExecCommand) > 0 {
240+
return runPostExec(ctx, res.PostExecCommand)
241+
}
242+
return 0
243+
}
244+
245+
func runPostExec(ctx context.Context, argv []string) int {
246+
if len(argv) == 0 || strings.TrimSpace(argv[0]) == "" {
247+
return 0
248+
}
249+
250+
run := func(cmd []string) error {
251+
c := exec.CommandContext(ctx, cmd[0], cmd[1:]...)
252+
c.Stdin = os.Stdin
253+
c.Stdout = os.Stdout
254+
c.Stderr = os.Stderr
255+
return c.Run()
256+
}
257+
258+
if err := run(argv); err != nil {
259+
var exitErr *exec.ExitError
260+
if errors.As(err, &exitErr) {
261+
return exitErr.ExitCode()
262+
}
263+
264+
// If we failed to execute a script directly (no shebang support / not executable),
265+
// retry via `php <script> ...`.
266+
if errors.Is(err, syscall.ENOEXEC) || errors.Is(err, syscall.EACCES) {
267+
phpArgv := append([]string{"php", argv[0]}, argv[1:]...)
268+
if err2 := run(phpArgv); err2 != nil {
269+
if errors.As(err2, &exitErr) {
270+
return exitErr.ExitCode()
271+
}
272+
fmt.Fprintln(os.Stderr, err2)
273+
return 1
274+
}
275+
return 0
276+
}
277+
233278
fmt.Fprintln(os.Stderr, err)
234279
return 1
235280
}

internal/domain/event.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import "time"
55
type EventType string
66

77
const (
8-
EventStepStart EventType = "step_start"
9-
EventStepDone EventType = "step_done"
10-
EventProgress EventType = "progress"
11-
EventLog EventType = "log"
12-
EventSteps EventType = "steps"
8+
EventStepStart EventType = "step_start"
9+
EventStepDone EventType = "step_done"
10+
EventProgress EventType = "progress"
11+
EventLog EventType = "log"
12+
EventSteps EventType = "steps"
1313
EventSystemStatus EventType = "system_status"
14-
EventWarning EventType = "warning"
15-
EventError EventType = "error"
14+
EventWarning EventType = "warning"
15+
EventError EventType = "error"
16+
EventExecRequest EventType = "exec_request"
1617
)
1718

1819
type Severity string
@@ -65,3 +66,10 @@ type StepsPayload struct {
6566
type SystemStatusEventPayload struct {
6667
SystemStatus SystemStatus
6768
}
69+
70+
// ExecRequestPayload requests that the UI quits and then launches a command after
71+
// Bubble Tea restores the terminal state.
72+
type ExecRequestPayload struct {
73+
// Command is an argv-style slice: Command[0] is the executable, the rest are args.
74+
Command []string
75+
}

internal/engine/install/engine.go

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"os/exec"
1414
"path/filepath"
1515
"regexp"
16+
"runtime"
1617
"strings"
1718
"sync"
1819
"time"
@@ -1492,7 +1493,7 @@ func (e *Engine) maybeOfferSelfUpdate(ctx context.Context, emit func(domain.Even
14921493
tag = "v" + info.HighestVersion
14931494
}
14941495

1495-
cmdStr := "go install github.com/evolution-cms/installer/cmd/evo@" + tag
1496+
cmdStr := "evo self-update"
14961497

14971498
_ = emit(domain.Event{
14981499
Type: domain.EventLog,
@@ -1519,9 +1520,14 @@ func (e *Engine) maybeOfferSelfUpdate(ctx context.Context, emit func(domain.Even
15191520

15201521
updateEnabled := true
15211522
reason := ""
1522-
if _, lookErr := exec.LookPath("go"); lookErr != nil {
1523+
bootstrapper, bootErr := findPHPBootstrapperEntry()
1524+
if bootErr != nil || strings.TrimSpace(bootstrapper) == "" {
15231525
updateEnabled = false
1524-
reason = "Go not found in PATH"
1526+
if bootErr != nil {
1527+
reason = bootErr.Error()
1528+
} else {
1529+
reason = "PHP bootstrapper not found"
1530+
}
15251531
}
15261532

15271533
choice, okSel := askSelect(ctx, emit, actions, stepID, domain.QuestionState{
@@ -1545,33 +1551,25 @@ func (e *Engine) maybeOfferSelfUpdate(ctx context.Context, emit func(domain.Even
15451551
Source: "install",
15461552
Severity: domain.SeverityInfo,
15471553
Payload: domain.LogPayload{
1548-
Message: "Updating installer…",
1554+
Message: "Exiting installer and running: " + cmdStr,
15491555
},
15501556
})
15511557

1552-
cmd := exec.CommandContext(ctx, "go", "install", "github.com/evolution-cms/installer/cmd/evo@"+tag)
1553-
out, err := cmd.CombinedOutput()
1554-
if err != nil {
1555-
_ = emit(domain.Event{
1556-
Type: domain.EventWarning,
1557-
StepID: stepID,
1558-
Source: "install",
1559-
Severity: domain.SeverityWarn,
1560-
Payload: domain.LogPayload{
1561-
Message: "Self-update failed; continuing installation.",
1562-
Fields: map[string]string{"error": err.Error(), "output": strings.TrimSpace(string(out))},
1563-
},
1564-
})
1558+
if !updateEnabled {
15651559
return false
15661560
}
15671561

1562+
cmd := []string{bootstrapper, "self-update"}
1563+
if runtime.GOOS == "windows" {
1564+
cmd = []string{"php", bootstrapper, "self-update"}
1565+
}
15681566
_ = emit(domain.Event{
1569-
Type: domain.EventLog,
1567+
Type: domain.EventExecRequest,
15701568
StepID: stepID,
15711569
Source: "install",
15721570
Severity: domain.SeverityInfo,
1573-
Payload: domain.LogPayload{
1574-
Message: "✔ Installer updated. Please restart the installer to use the new version.",
1571+
Payload: domain.ExecRequestPayload{
1572+
Command: cmd,
15751573
},
15761574
})
15771575

internal/ui/model.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ type Model struct {
6363

6464
confirmQuitActive bool
6565
confirmQuitSelected int // 0 = abort, 1 = continue
66+
67+
// When set by the engine, quit the UI and run this command after the TUI exits.
68+
postExecCommand []string
69+
quitRequested bool
6670
}
6771

6872
func NewModel(ctx context.Context, mode Mode, events <-chan domain.Event, actions chan<- domain.Action, meta Meta, cancel func()) *Model {
@@ -329,6 +333,13 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
329333
}
330334
m.applyEvent(msg.Event)
331335
m.reflow()
336+
if m.quitRequested {
337+
// Cancel the engine context (best-effort) and quit the TUI immediately.
338+
if m.cancel != nil {
339+
m.cancel()
340+
}
341+
return m, tea.Quit
342+
}
332343
return m, waitForEvent(m.events)
333344

334345
default:
@@ -356,6 +367,11 @@ func (m *Model) requestCancel(key string) {
356367

357368
func (m *Model) applyEvent(ev domain.Event) {
358369
switch ev.Type {
370+
case domain.EventExecRequest:
371+
if p, ok := ev.Payload.(domain.ExecRequestPayload); ok && len(p.Command) > 0 {
372+
m.postExecCommand = append([]string(nil), p.Command...)
373+
m.quitRequested = true
374+
}
359375
case domain.EventSteps:
360376
switch p := ev.Payload.(type) {
361377
case domain.StepsPayload:

internal/ui/run.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@ import (
1414
"github.com/evolution-cms/installer/internal/domain"
1515
)
1616

17+
type RunResult struct {
18+
PostExecCommand []string
19+
}
20+
1721
func Run(ctx context.Context, mode Mode, events <-chan domain.Event, meta Meta) error {
18-
return RunWithCancel(ctx, mode, events, nil, meta, nil)
22+
_, err := RunWithCancel(ctx, mode, events, nil, meta, nil)
23+
return err
1924
}
2025

21-
func RunWithCancel(ctx context.Context, mode Mode, events <-chan domain.Event, actions chan<- domain.Action, meta Meta, cancel func()) error {
26+
func RunWithCancel(ctx context.Context, mode Mode, events <-chan domain.Event, actions chan<- domain.Action, meta Meta, cancel func()) (RunResult, error) {
2227
in := os.Stdin
2328
out := os.Stdout
2429
var tty *os.File
@@ -55,8 +60,12 @@ func RunWithCancel(ctx context.Context, mode Mode, events <-chan domain.Event, a
5560
m.reflow()
5661

5762
p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithInput(in), tea.WithOutput(out))
58-
_, err := p.Run()
59-
return err
63+
finalModel, err := p.Run()
64+
res := RunResult{}
65+
if mm, ok := finalModel.(*Model); ok && len(mm.postExecCommand) > 0 {
66+
res.PostExecCommand = append([]string(nil), mm.postExecCommand...)
67+
}
68+
return res, err
6069
}
6170

6271
func configureTerminalOutput(out *os.File) {

0 commit comments

Comments
 (0)