Files
elmprodvpn/selective-vpn-api/app/transport_backends_adapter_systemd_singbox_template.go

169 lines
5.9 KiB
Go

package app
import (
"fmt"
"os"
"path/filepath"
"strings"
transportcfg "selective-vpn-api/app/transportcfg"
)
const (
transportSingBoxTemplateUnitName = "singbox@.service"
transportSingBoxInstanceDropIn = "10-selective-vpn.conf"
transportSingBoxTemplateMarker = "# SVPN_TEMPLATE=singbox-instance"
transportSingBoxInstanceDropInTag = "# SVPN_INSTANCE_DROPIN=singbox"
)
func transportSystemdSingBoxUsesTemplateInstance(client TransportClient, unit string) bool {
if client.Kind != TransportClientSingBox {
return false
}
u := strings.TrimSpace(unit)
if u == "" || !strings.HasSuffix(strings.ToLower(u), ".service") {
return false
}
at := strings.IndexByte(u, '@')
return at > 0
}
func transportSystemdTemplateUnitForInstance(unit string) string {
u := strings.TrimSpace(unit)
if !strings.HasSuffix(strings.ToLower(u), ".service") {
return ""
}
at := strings.IndexByte(u, '@')
if at <= 0 {
return ""
}
return u[:at] + "@.service"
}
func transportSystemdUnitDropInDir(unit string) string {
path := transportSystemdUnitPath(strings.TrimSpace(unit))
return path + ".d"
}
func transportSystemdUnitDropInPath(unit, fileName string) string {
name := strings.TrimSpace(fileName)
if name == "" {
name = transportSingBoxInstanceDropIn
}
return filepath.Join(transportSystemdUnitDropInDir(unit), name)
}
func writeTransportSystemdDropInFile(unit, fileName, content string) error {
path := transportSystemdUnitDropInPath(unit, fileName)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
tmp := path + ".tmp"
if err := os.WriteFile(tmp, []byte(content), 0o644); err != nil {
return err
}
return os.Rename(tmp, path)
}
func writeTransportSingBoxTemplateArtifacts(
client TransportClient,
in transportSystemdProvisionInputs,
) error {
templateUnit := transportSystemdTemplateUnitForInstance(in.unit)
if templateUnit == "" {
templateUnit = transportSingBoxTemplateUnitName
}
if !validSystemdUnitName(templateUnit) {
return fmt.Errorf("invalid template unit: %s", templateUnit)
}
templateTuning := transportSystemdServiceTuningFromConfig(nil, "")
templateHardening := transportSystemdHardeningFromConfig(nil, "")
templateContent := renderTransportSystemdUnit(
TransportClient{ID: "%i", Kind: TransportClientSingBox},
templateUnit,
"/bin/true",
false,
"",
templateTuning,
templateHardening,
)
templateContent = transportSingBoxTemplateMarker + "\n" + templateContent
if err := writeTransportSystemdUnitFile(templateUnit, templateContent); err != nil {
return err
}
netnsEnabled := transportNetnsEnabled(client)
netnsName := ""
if netnsEnabled {
netnsName = transportNetnsName(client)
}
configPath := transportSingBoxConfigPath(client)
dropIn := renderTransportSingBoxInstanceDropIn(client, in, configPath, netnsEnabled, netnsName)
return writeTransportSystemdDropInFile(in.unit, transportSingBoxInstanceDropIn, dropIn)
}
func renderTransportSingBoxInstanceDropIn(
client TransportClient,
in transportSystemdProvisionInputs,
configPath string,
netnsEnabled bool,
netnsName string,
) string {
b := strings.Builder{}
b.WriteString(transportSingBoxInstanceDropInTag + "\n")
b.WriteString("[Unit]\n")
b.WriteString("StartLimitIntervalSec=" + fmt.Sprintf("%d", in.primaryTuning.StartLimitIntervalSec) + "\n")
b.WriteString("StartLimitBurst=" + fmt.Sprintf("%d", in.primaryTuning.StartLimitBurst) + "\n")
b.WriteString("\n[Service]\n")
b.WriteString("WorkingDirectory=" + stateDir + "\n")
b.WriteString("Restart=" + in.primaryTuning.RestartPolicy + "\n")
b.WriteString("RestartSec=" + fmt.Sprintf("%d", in.primaryTuning.RestartSec) + "\n")
b.WriteString("Environment=SVPN_TRANSPORT_ID=" + strings.TrimSpace(client.ID) + "\n")
b.WriteString("Environment=SVPN_TRANSPORT_KIND=" + strings.TrimSpace(string(client.Kind)) + "\n")
if strings.TrimSpace(configPath) != "" {
b.WriteString("Environment=SVPN_CONFIG_PATH=" + strings.TrimSpace(configPath) + "\n")
}
if netnsEnabled {
b.WriteString("Environment=SVPN_NETNS_ENABLED=1\n")
if strings.TrimSpace(netnsName) != "" {
b.WriteString("Environment=SVPN_NETNS_NAME=" + strings.TrimSpace(netnsName) + "\n")
}
} else {
b.WriteString("Environment=SVPN_NETNS_ENABLED=0\n")
}
b.WriteString("ExecStart=\n")
b.WriteString("ExecStart=" + transportcfg.SystemdShellExec(in.primaryCmd, shellQuoteArg) + "\n")
b.WriteString("ExecStop=\n")
b.WriteString("ExecStop=/bin/kill -TERM $MAINPID\n")
b.WriteString("TimeoutStartSec=" + fmt.Sprintf("%d", in.primaryTuning.TimeoutStartSec) + "\n")
b.WriteString("TimeoutStopSec=" + fmt.Sprintf("%d", in.primaryTuning.TimeoutStopSec) + "\n")
if in.primaryTuning.WatchdogSec > 0 {
b.WriteString("WatchdogSec=" + fmt.Sprintf("%d", in.primaryTuning.WatchdogSec) + "\n")
b.WriteString("NotifyAccess=main\n")
}
if in.primaryHardening.Enabled {
b.WriteString("NoNewPrivileges=" + transportSystemdBool(in.primaryHardening.NoNewPrivileges) + "\n")
b.WriteString("PrivateTmp=" + transportSystemdBool(in.primaryHardening.PrivateTmp) + "\n")
b.WriteString("ProtectSystem=" + in.primaryHardening.ProtectSystem + "\n")
b.WriteString("ProtectHome=" + in.primaryHardening.ProtectHome + "\n")
b.WriteString("ProtectControlGroups=" + transportSystemdBool(in.primaryHardening.ProtectControlGroups) + "\n")
b.WriteString("ProtectKernelModules=" + transportSystemdBool(in.primaryHardening.ProtectKernelModules) + "\n")
b.WriteString("ProtectKernelTunables=" + transportSystemdBool(in.primaryHardening.ProtectKernelTunables) + "\n")
b.WriteString("RestrictSUIDSGID=" + transportSystemdBool(in.primaryHardening.RestrictSUIDSGID) + "\n")
b.WriteString("LockPersonality=" + transportSystemdBool(in.primaryHardening.LockPersonality) + "\n")
if in.primaryHardening.PrivateDevices {
b.WriteString("PrivateDevices=yes\n")
}
b.WriteString("UMask=" + in.primaryHardening.UMask + "\n")
}
return b.String()
}
func transportSystemdBool(v bool) string {
if v {
return "yes"
}
return "no"
}