mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
340 lines
8.5 KiB
Go
340 lines
8.5 KiB
Go
package router
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/kardianos/service"
|
|
)
|
|
|
|
// This is a copy of https://github.com/kardianos/service/blob/v1.2.1/service_sysv_linux.go,
|
|
// with modification for supporting ubios v1 init system.
|
|
|
|
// Keep in sync with ubios.ubiosDNSMasqConfigPath
|
|
const ubiosDNSMasqConfigPath = "/run/dnsmasq.conf.d/zzzctrld.conf"
|
|
|
|
type ubiosSvc struct {
|
|
i service.Interface
|
|
platform string
|
|
*service.Config
|
|
}
|
|
|
|
func newUbiosService(i service.Interface, platform string, c *service.Config) (service.Service, error) {
|
|
s := &ubiosSvc{
|
|
i: i,
|
|
platform: platform,
|
|
Config: c,
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (s *ubiosSvc) String() string {
|
|
if len(s.DisplayName) > 0 {
|
|
return s.DisplayName
|
|
}
|
|
return s.Name
|
|
}
|
|
|
|
func (s *ubiosSvc) Platform() string {
|
|
return s.platform
|
|
}
|
|
|
|
func (s *ubiosSvc) configPath() string {
|
|
return "/etc/init.d/" + s.Config.Name
|
|
}
|
|
|
|
func (s *ubiosSvc) execPath() (string, error) {
|
|
if len(s.Executable) != 0 {
|
|
return filepath.Abs(s.Executable)
|
|
}
|
|
return os.Executable()
|
|
}
|
|
|
|
func (s *ubiosSvc) template() *template.Template {
|
|
return template.Must(template.New("").Funcs(tf).Parse(ubiosSvcScript))
|
|
}
|
|
|
|
func (s *ubiosSvc) Install() error {
|
|
confPath := s.configPath()
|
|
if _, err := os.Stat(confPath); err == nil {
|
|
return fmt.Errorf("init already exists: %s", confPath)
|
|
}
|
|
|
|
f, err := os.Create(confPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create config path: %w", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
path, err := s.execPath()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get exec path: %w", err)
|
|
}
|
|
|
|
var to = &struct {
|
|
*service.Config
|
|
Path string
|
|
DnsMasqConfPath string
|
|
}{
|
|
s.Config,
|
|
path,
|
|
ubiosDNSMasqConfigPath,
|
|
}
|
|
|
|
if err := s.template().Execute(f, to); err != nil {
|
|
return fmt.Errorf("failed to create init script: %w", err)
|
|
}
|
|
|
|
if err := f.Close(); err != nil {
|
|
return fmt.Errorf("failed to save init script: %w", err)
|
|
}
|
|
|
|
if err = os.Chmod(confPath, 0755); err != nil {
|
|
return fmt.Errorf("failed to set init script executable: %w", err)
|
|
}
|
|
|
|
// Enable on boot
|
|
script, err := os.CreateTemp("", "ctrld_boot.service")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create boot service tmp file: %w", err)
|
|
}
|
|
defer script.Close()
|
|
|
|
svcConfig := *to.Config
|
|
svcConfig.Arguments = os.Args[1:]
|
|
to.Config = &svcConfig
|
|
if err := template.Must(template.New("").Funcs(tf).Parse(ubiosBootSystemdService)).Execute(script, &to); err != nil {
|
|
return fmt.Errorf("failed to create boot service file: %w", err)
|
|
}
|
|
if err := script.Close(); err != nil {
|
|
return fmt.Errorf("failed to save boot service file: %w", err)
|
|
}
|
|
|
|
// Copy the boot script to container and start.
|
|
cmd := exec.Command("podman", "cp", "--pause=false", script.Name(), "unifi-os:/lib/systemd/system/ctrld-boot.service")
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to copy boot script, out: %s, err: %v", string(out), err)
|
|
}
|
|
cmd = exec.Command("podman", "exec", "unifi-os", "systemctl", "enable", "--now", "ctrld-boot.service")
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to start ctrld boot script, out: %s, err: %v", string(out), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ubiosSvc) Uninstall() error {
|
|
if err := os.Remove(s.configPath()); err != nil {
|
|
return err
|
|
}
|
|
// Remove ctrld-boot service inside unifi-os container.
|
|
cmd := exec.Command("podman", "exec", "unifi-os", "systemctl", "disable", "ctrld-boot.service")
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to disable ctrld-boot service, out: %s, err: %v", string(out), err)
|
|
}
|
|
cmd = exec.Command("podman", "exec", "unifi-os", "rm", "/lib/systemd/system/ctrld-boot.service")
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to remove ctrld-boot service file, out: %s, err: %v", string(out), err)
|
|
}
|
|
cmd = exec.Command("podman", "exec", "unifi-os", "systemctl", "daemon-reload")
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to reload systemd service, out: %s, err: %v", string(out), err)
|
|
}
|
|
cmd = exec.Command("podman", "exec", "unifi-os", "systemctl", "reset-failed")
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to reset-failed systemd service, out: %s, err: %v", string(out), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ubiosSvc) Logger(errs chan<- error) (service.Logger, error) {
|
|
if service.Interactive() {
|
|
return service.ConsoleLogger, nil
|
|
}
|
|
return s.SystemLogger(errs)
|
|
}
|
|
|
|
func (s *ubiosSvc) SystemLogger(errs chan<- error) (service.Logger, error) {
|
|
return newSysLogger(s.Name, errs)
|
|
}
|
|
|
|
func (s *ubiosSvc) Run() (err error) {
|
|
err = s.i.Start(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if interactice, _ := isInteractive(); !interactice {
|
|
signal.Ignore(syscall.SIGHUP)
|
|
}
|
|
|
|
var sigChan = make(chan os.Signal, 3)
|
|
signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt)
|
|
<-sigChan
|
|
|
|
return s.i.Stop(s)
|
|
}
|
|
|
|
func (s *ubiosSvc) Status() (service.Status, error) {
|
|
if _, err := os.Stat(s.configPath()); os.IsNotExist(err) {
|
|
return service.StatusUnknown, service.ErrNotInstalled
|
|
}
|
|
out, err := exec.Command(s.configPath(), "status").CombinedOutput()
|
|
if err != nil {
|
|
return service.StatusUnknown, err
|
|
}
|
|
switch string(bytes.TrimSpace(out)) {
|
|
case "Running":
|
|
return service.StatusRunning, nil
|
|
default:
|
|
return service.StatusStopped, nil
|
|
}
|
|
}
|
|
|
|
func (s *ubiosSvc) Start() error {
|
|
return exec.Command(s.configPath(), "start").Run()
|
|
}
|
|
|
|
func (s *ubiosSvc) Stop() error {
|
|
return exec.Command(s.configPath(), "stop").Run()
|
|
}
|
|
|
|
func (s *ubiosSvc) Restart() error {
|
|
err := s.Stop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
time.Sleep(50 * time.Millisecond)
|
|
return s.Start()
|
|
}
|
|
|
|
const ubiosBootSystemdService = `[Unit]
|
|
Description=Run ctrld On Startup UDM
|
|
Wants=network-online.target
|
|
After=network-online.target
|
|
StartLimitIntervalSec=500
|
|
StartLimitBurst=5
|
|
|
|
[Service]
|
|
Restart=on-failure
|
|
RestartSec=5s
|
|
ExecStart=/sbin/ssh-proxy '[ -f "{{.DnsMasqConfPath}}" ] || {{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}'
|
|
RemainAfterExit=true
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
`
|
|
|
|
const ubiosSvcScript = `#!/bin/sh
|
|
# For RedHat and cousins:
|
|
# chkconfig: - 99 01
|
|
# description: {{.Description}}
|
|
# processname: {{.Path}}
|
|
|
|
### BEGIN INIT INFO
|
|
# Provides: {{.Path}}
|
|
# Required-Start:
|
|
# Required-Stop:
|
|
# Default-Start: 2 3 4 5
|
|
# Default-Stop: 0 1 6
|
|
# Short-Description: {{.DisplayName}}
|
|
# Description: {{.Description}}
|
|
### END INIT INFO
|
|
|
|
cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}"
|
|
|
|
name=$(basename $(readlink -f $0))
|
|
pid_file="/var/run/$name.pid"
|
|
stdout_log="/var/log/$name.log"
|
|
stderr_log="/var/log/$name.err"
|
|
|
|
[ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name
|
|
|
|
get_pid() {
|
|
cat "$pid_file"
|
|
}
|
|
|
|
is_running() {
|
|
[ -f "$pid_file" ] && cat /proc/$(get_pid)/stat > /dev/null 2>&1
|
|
}
|
|
|
|
case "$1" in
|
|
start)
|
|
if is_running; then
|
|
echo "Already started"
|
|
else
|
|
echo "Starting $name"
|
|
{{if .WorkingDirectory}}cd '{{.WorkingDirectory}}'{{end}}
|
|
$cmd >> "$stdout_log" 2>> "$stderr_log" &
|
|
echo $! > "$pid_file"
|
|
if ! is_running; then
|
|
echo "Unable to start, see $stdout_log and $stderr_log"
|
|
exit 1
|
|
fi
|
|
fi
|
|
;;
|
|
stop)
|
|
if is_running; then
|
|
echo -n "Stopping $name.."
|
|
kill $(get_pid)
|
|
for i in $(seq 1 10)
|
|
do
|
|
if ! is_running; then
|
|
break
|
|
fi
|
|
echo -n "."
|
|
sleep 1
|
|
done
|
|
echo
|
|
if is_running; then
|
|
echo "Not stopped; may still be shutting down or shutdown may have failed"
|
|
exit 1
|
|
else
|
|
echo "Stopped"
|
|
if [ -f "$pid_file" ]; then
|
|
rm "$pid_file"
|
|
fi
|
|
fi
|
|
else
|
|
echo "Not running"
|
|
fi
|
|
;;
|
|
restart)
|
|
$0 stop
|
|
if is_running; then
|
|
echo "Unable to stop, will not attempt to start"
|
|
exit 1
|
|
fi
|
|
$0 start
|
|
;;
|
|
status)
|
|
if is_running; then
|
|
echo "Running"
|
|
else
|
|
echo "Stopped"
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
echo "Usage: $0 {start|stop|restart|status}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
exit 0
|
|
`
|
|
|
|
var tf = map[string]interface{}{
|
|
"cmd": func(s string) string {
|
|
return `"` + strings.Replace(s, `"`, `\"`, -1) + `"`
|
|
},
|
|
"cmdEscape": func(s string) string {
|
|
return strings.Replace(s, " ", `\x20`, -1)
|
|
},
|
|
}
|