169 lines
5.9 KiB
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"
|
|
}
|