174 lines
5.0 KiB
Go
174 lines
5.0 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
func (transportSystemdBackend) Cleanup(client TransportClient) transportBackendActionResult {
|
|
unit := transportBackendUnit(client)
|
|
if !validSystemdUnitName(unit) {
|
|
return transportBackendActionResult{
|
|
OK: false,
|
|
Code: "TRANSPORT_BACKEND_UNIT_REQUIRED",
|
|
Message: "valid systemd unit is required for cleanup",
|
|
ExitCode: -1,
|
|
}
|
|
}
|
|
|
|
units := []string{unit}
|
|
if transportSystemdSingBoxUsesTemplateInstance(client, unit) {
|
|
units = transportSystemdAppendUniqueUnits(units, transportSystemdLegacySingBoxUnitCandidates(client, unit))
|
|
}
|
|
if transportDNSTTSSHTunnelEnabled(client) {
|
|
sshUnit := transportDNSTTSSHUnit(client)
|
|
if !validSystemdUnitName(sshUnit) {
|
|
return transportBackendActionResult{
|
|
OK: false,
|
|
Code: "TRANSPORT_BACKEND_UNIT_REQUIRED",
|
|
Message: "valid config.ssh_unit is required for cleanup",
|
|
ExitCode: -1,
|
|
}
|
|
}
|
|
units = append(units, sshUnit)
|
|
}
|
|
|
|
ownedUnits := make([]string, 0, len(units))
|
|
out := make([]string, 0, len(units)*2)
|
|
errOut := make([]string, 0, len(units)*2)
|
|
|
|
for _, u := range units {
|
|
path := transportSystemdUnitPath(u)
|
|
dropInMode := false
|
|
dropInDir := ""
|
|
if transportSystemdSingBoxUsesTemplateInstance(client, u) {
|
|
path = transportSystemdUnitDropInPath(u, transportSingBoxInstanceDropIn)
|
|
dropInMode = true
|
|
dropInDir = transportSystemdUnitDropInDir(u)
|
|
}
|
|
owned, err := transportSystemdUnitOwnedByClient(path, client.ID)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
errOut = append(errOut, fmt.Sprintf("%s ownership check failed: %v", u, err))
|
|
continue
|
|
}
|
|
if !owned {
|
|
// Safety: never touch units we did not provision for this client id.
|
|
continue
|
|
}
|
|
ownedUnits = append(ownedUnits, u)
|
|
|
|
stdout, stderr, code, runErr := transportRunCommand(transportBackendActionTimeout, "systemctl", "stop", u)
|
|
if s := strings.TrimSpace(stdout); s != "" {
|
|
out = append(out, u+" stop: "+s)
|
|
}
|
|
if s := strings.TrimSpace(stderr); s != "" {
|
|
errOut = append(errOut, u+" stop: "+s)
|
|
}
|
|
if runErr != nil || code != 0 {
|
|
errOut = append(errOut, fmt.Sprintf("%s stop failed (exit=%d)", u, code))
|
|
}
|
|
|
|
stdout, stderr, code, runErr = transportRunCommand(transportBackendActionTimeout, "systemctl", "disable", u)
|
|
if s := strings.TrimSpace(stdout); s != "" {
|
|
out = append(out, u+" disable: "+s)
|
|
}
|
|
if s := strings.TrimSpace(stderr); s != "" {
|
|
errOut = append(errOut, u+" disable: "+s)
|
|
}
|
|
if runErr != nil || code != 0 {
|
|
errOut = append(errOut, fmt.Sprintf("%s disable failed (exit=%d)", u, code))
|
|
}
|
|
|
|
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
|
errOut = append(errOut, fmt.Sprintf("%s remove failed: %v", u, err))
|
|
}
|
|
if dropInMode && strings.TrimSpace(dropInDir) != "" {
|
|
if err := os.Remove(dropInDir); err != nil && !os.IsNotExist(err) {
|
|
msg := strings.ToLower(strings.TrimSpace(err.Error()))
|
|
if !strings.Contains(msg, "directory not empty") {
|
|
errOut = append(errOut, fmt.Sprintf("%s drop-in dir remove failed: %v", u, err))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(ownedUnits) > 0 {
|
|
stdout, stderr, code, runErr := transportRunCommand(transportBackendActionTimeout, "systemctl", "daemon-reload")
|
|
if s := strings.TrimSpace(stdout); s != "" {
|
|
out = append(out, "daemon-reload: "+s)
|
|
}
|
|
if s := strings.TrimSpace(stderr); s != "" {
|
|
errOut = append(errOut, "daemon-reload: "+s)
|
|
}
|
|
if runErr != nil || code != 0 {
|
|
errOut = append(errOut, fmt.Sprintf("daemon-reload failed (exit=%d)", code))
|
|
}
|
|
|
|
for _, u := range ownedUnits {
|
|
_, stderr, code, runErr := transportRunCommand(transportBackendActionTimeout, "systemctl", "reset-failed", u)
|
|
if s := strings.TrimSpace(stderr); s != "" {
|
|
errOut = append(errOut, u+" reset-failed: "+s)
|
|
}
|
|
if runErr != nil || code != 0 {
|
|
errOut = append(errOut, fmt.Sprintf("%s reset-failed failed (exit=%d)", u, code))
|
|
}
|
|
}
|
|
}
|
|
|
|
if transportNetnsEnabled(client) {
|
|
msg, err := transportCleanupNetnsForClient(client)
|
|
if s := strings.TrimSpace(msg); s != "" {
|
|
out = append(out, "netns: "+s)
|
|
}
|
|
if err != nil {
|
|
errOut = append(errOut, "netns: "+err.Error())
|
|
}
|
|
}
|
|
|
|
msg := fmt.Sprintf("cleanup done for %d managed units", len(ownedUnits))
|
|
if len(ownedUnits) == 0 {
|
|
msg = "no managed unit artifacts found"
|
|
}
|
|
if len(errOut) > 0 {
|
|
return transportBackendActionResult{
|
|
OK: false,
|
|
Code: "TRANSPORT_BACKEND_CLEANUP_FAILED",
|
|
Message: msg,
|
|
ExitCode: -1,
|
|
Stdout: strings.Join(out, "\n"),
|
|
Stderr: strings.Join(errOut, "\n"),
|
|
Retryable: true,
|
|
}
|
|
}
|
|
return transportBackendActionResult{
|
|
OK: true,
|
|
ExitCode: 0,
|
|
Message: msg,
|
|
Stdout: strings.Join(out, "\n"),
|
|
}
|
|
}
|
|
|
|
func transportSystemdAppendUniqueUnits(dst []string, candidates []string) []string {
|
|
for _, candidate := range candidates {
|
|
u := strings.TrimSpace(candidate)
|
|
if u == "" {
|
|
continue
|
|
}
|
|
already := false
|
|
for _, existing := range dst {
|
|
if strings.EqualFold(strings.TrimSpace(existing), u) {
|
|
already = true
|
|
break
|
|
}
|
|
}
|
|
if !already {
|
|
dst = append(dst, u)
|
|
}
|
|
}
|
|
return dst
|
|
}
|