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
package tunservice
import (
"errors"
"fmt"
"os"
"os/exec"
"github.com/armanlife/firefox-vpn-client/core/internal/tunapi"
)
// InstallDir is the installed service's folder.
const InstallDir = "/opt/firefox-vpn-tun"
const unitName = tunapi.ServiceName + ".service"
// unit is the service's systemd unit. Restart=always starts it again after
// a crash and after an update (which ends it); an uninstall stops it for
// good (stopForGood).
const unit = `[Unit]
Description=Firefox VPN TUN
Documentation=https://github.com/armanlife/firefox-vpn-client
After=network.target
[Service]
Type=simple
ExecStart=%s service
Restart=always
RestartSec=2
[Install]
WantedBy=multi-user.target
`
// initSystemPresent: the service needs systemd as the init system.
func initSystemPresent() error {
if _, err := os.Stat("/run/systemd/system"); err != nil {
return errors.New("the TUN mode needs systemd, which does not run this system")
}
if _, err := exec.LookPath("systemctl"); err != nil {
return errors.New("the TUN mode needs systemd's systemctl, which is not found")
}
return nil
}
// register writes the unit for the executable exe, then enables and
// starts it.
func register(exe string) error {
if err := replaceFile(tunapi.ServiceFile, []byte(fmt.Sprintf(unit, systemdQuote(exe))), 0o644); err != nil {
return err
}
if err := run("systemctl", "daemon-reload"); err != nil {
return err
}
if err := run("systemctl", "enable", unitName); err != nil {
return err
}
return run("systemctl", "restart", unitName)
}
// unregister disables the unit and removes it; it does not stop the
// service, which may be the caller.
func unregister() error {
if installed, _ := tunapi.Installed(); !installed {
return nil
}
_ = run("systemctl", "disable", unitName)
if err := os.Remove(tunapi.ServiceFile); err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
return run("systemctl", "daemon-reload")
}
// stopInstalled stops the installed service and waits for it.
func stopInstalled() error {
if installed, _ := tunapi.Installed(); !installed {
return nil
}
return run("systemctl", "stop", unitName)
}
// stopForGood has systemd stop the service, whose unit is removed, so it is
// not started again when this process ends.
func stopForGood() {
_ = run("systemctl", "--no-block", "stop", unitName)
}
// systemdQuote quotes a path for ExecStart: in double quotes, with
// backslash and double quote escaped, and the specifier and variable signs
// doubled.
func systemdQuote(path string) string {
out := []byte{'"'}
for i := 0; i < len(path); i++ {
switch c := path[i]; c {
case '\\', '"':
out = append(out, '\\', c)
case '%', '$':
out = append(out, c, c)
default:
out = append(out, c)
}
}
return string(append(out, '"'))
}