aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/app/setup.go
blob: 7b5cd2eca8cd9277afb5d110f0ef6ebdac7938a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package app

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"os/user"
	"strconv"

	"git.ophivana.moe/cat/fortify/internal/state"
	"git.ophivana.moe/cat/fortify/internal/system"
	"git.ophivana.moe/cat/fortify/internal/util"
)

type App struct {
	uid     int
	env     []string
	command []string

	*user.User
}

func (a *App) Run() {
	f := a.launchBySudo
	m, b := false, false
	switch {
	case system.MethodFlags[0]: // sudo
	case system.MethodFlags[1]: // bare
		m, b = true, true
	default: // machinectl
		m, b = true, false
	}

	var toolPath string

	// dependency checks
	const sudoFallback = "Falling back to 'sudo', some desktop integration features may not work"
	if m {
		if !util.SdBooted() {
			fmt.Println("This system was not booted through systemd")
			fmt.Println(sudoFallback)
		} else if tp, ok := util.Which("machinectl"); !ok {
			fmt.Println("Did not find 'machinectl' in PATH")
			fmt.Println(sudoFallback)
		} else {
			toolPath = tp
			f = func() []string { return a.launchByMachineCtl(b) }
		}
	} else if tp, ok := util.Which("sudo"); !ok {
		state.Fatal("Did not find 'sudo' in PATH")
	} else {
		toolPath = tp
	}

	if system.V.Verbose {
		fmt.Printf("Selected launcher '%s' bare=%t\n", toolPath, b)
	}

	cmd := exec.Command(toolPath, f()...)
	cmd.Env = a.env
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = system.V.RunDir

	if system.V.Verbose {
		fmt.Println("Executing:", cmd)
	}

	if err := cmd.Start(); err != nil {
		state.Fatal("Error starting process:", err)
	}

	if err := state.SaveProcess(a.Uid, cmd); err != nil {
		// process already started, shouldn't be fatal
		fmt.Println("Error registering process:", err)
	}

	var r int
	if err := cmd.Wait(); err != nil {
		var exitError *exec.ExitError
		if !errors.As(err, &exitError) {
			state.Fatal("Error running process:", err)
		}
	}

	if system.V.Verbose {
		fmt.Println("Process exited with exit code", r)
	}
	state.BeforeExit()
	os.Exit(r)
}

func New(userName string, args []string) *App {
	a := &App{command: args}

	if u, err := user.Lookup(userName); err != nil {
		if errors.As(err, new(user.UnknownUserError)) {
			fmt.Println("unknown user", userName)
		} else {
			// unreachable
			panic(err)
		}

		// too early for fatal
		os.Exit(1)
	} else {
		a.User = u
	}

	if u, err := strconv.Atoi(a.Uid); err != nil {
		// usually unreachable
		panic("uid parse")
	} else {
		a.uid = u
	}

	if system.V.Verbose {
		fmt.Println("Running as user", a.Username, "("+a.Uid+"),", "command:", a.command)
	}

	return a
}