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 }