aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/app/setup.go
blob: 8d6eb88199208809e830ea8019d1eefc525f0c43 (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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package app

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

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

const (
	xdgRuntimeDir = "XDG_RUNTIME_DIR"
)

type App struct {
	uid     int      // assigned
	env     []string // modified via AppendEnv
	command []string // set on initialisation

	launchOptionText string // set on initialisation
	launchOption     uint8  // assigned

	sharePath   string // set on initialisation
	runtimePath string // assigned
	runDirPath  string // assigned
	toolPath    string // assigned

	enablements state.Enablements // set via setEnablement
	*user.User                    // assigned

	// absolutely *no* method of this type is thread-safe
	// so don't treat it as if it is
}

func (a *App) LaunchOption() uint8 {
	return a.launchOption
}

func (a *App) RunDir() string {
	return a.runDirPath
}

func (a *App) setEnablement(e state.Enablement) {
	if a.enablements.Has(e) {
		panic("enablement " + e.String() + " set twice")
	}

	a.enablements |= e.Mask()
}

func New(userName string, args []string, launchOptionText string) *App {
	a := &App{
		command:          args,
		launchOptionText: launchOptionText,
		sharePath:        path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Geteuid())),
	}

	// runtimePath, runDirPath
	if r, ok := os.LookupEnv(xdgRuntimeDir); !ok {
		fmt.Println("Env variable", xdgRuntimeDir, "unset")

		// too early for fatal
		os.Exit(1)
	} else {
		a.runtimePath = r
		a.runDirPath = path.Join(a.runtimePath, "fortify")
		verbose.Println("Runtime directory at", a.runDirPath)
	}

	// *user.User
	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
	}

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

	verbose.Println("Running as user", a.Username, "("+a.Uid+"),", "command:", a.command)
	if util.SdBootedV {
		verbose.Println("System booted with systemd as init system (PID 1).")
	}

	// launchOption, toolPath
	switch a.launchOptionText {
	case "sudo":
		a.launchOption = LaunchMethodSudo
		if sudoPath, ok := util.Which("sudo"); !ok {
			fmt.Println("Did not find 'sudo' in PATH")
			os.Exit(1)
		} else {
			a.toolPath = sudoPath
		}
	case "bubblewrap":
		a.launchOption = LaunchMethodBwrap
		if bwrapPath, ok := util.Which("bwrap"); !ok {
			fmt.Println("Did not find 'bwrap' in PATH")
			os.Exit(1)
		} else {
			a.toolPath = bwrapPath
		}
	case "systemd":
		a.launchOption = LaunchMethodMachineCtl
		if !util.SdBootedV {
			fmt.Println("System has not been booted with systemd as init system (PID 1).")
			os.Exit(1)
		}

		if machineCtlPath, ok := util.Which("machinectl"); !ok {
			fmt.Println("Did not find 'machinectl' in PATH")
		} else {
			a.toolPath = machineCtlPath
		}
	default:
		fmt.Println("invalid launch method")
		os.Exit(1)
	}

	verbose.Println("Determined launch method to be", a.launchOptionText, "with tool at", a.toolPath)

	return a
}