Files
Sanaei f727d04f65
CI / go-test (push) Canceled after 0s
CI / postgres-durable-first (push) Canceled after 0s
CI / codegen (push) Canceled after 0s
CI / govulncheck (push) Canceled after 0s
CI / race (push) Canceled after 0s
CI / fuzz-smoke (push) Canceled after 0s
CI / golangci (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CodeQL Advanced / Analyze (go) (push) Canceled after 0s
CodeQL Advanced / Analyze (actions) (push) Canceled after 0s
CodeQL Advanced / Analyze (javascript-typescript) (push) Canceled after 0s
Docs CI / build (push) Canceled after 0s
Docs Deploy (GitHub Pages) / build (push) Canceled after 0s
Release 3X-UI / build (386) (push) Canceled after 0s
Release 3X-UI / build (amd64) (push) Canceled after 0s
Release 3X-UI / build (arm64) (push) Canceled after 0s
Release 3X-UI / build (armv5) (push) Canceled after 0s
Release 3X-UI / build (armv6) (push) Canceled after 0s
Release 3X-UI / build (armv7) (push) Canceled after 0s
Release 3X-UI / build (s390x) (push) Canceled after 0s
Release 3X-UI / Build for Windows (amd64) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-latest) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-latest) (push) Canceled after 0s
Docs Deploy (GitHub Pages) / deploy (push) Canceled after 0s
Release 3X-UI / Publish rolling dev release (push) Canceled after 0s
v3.7.0
2026-08-24 15:07:15 +02:00

80 lines
2.3 KiB
Go

//go:build linux
package mtproto
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
)
// killStrayMtgProcesses terminates orphaned mtg sidecars left over from a
// previous x-ui run and returns how many were killed.
//
// x-ui starts one mtg process per mtproto inbound outside its own lifecycle, and
// on Linux a child is not guaranteed to die with the panel (there is no
// kill-on-exit, unlike the Windows job object). A survivor keeps holding the
// inbound port with a now-stale secret, so new clients are silently
// domain-fronted to the FakeTLS domain instead of proxied to Telegram. x-ui is
// the sole owner of mtg, so any process matching our binary name at startup is
// an orphan and is safe to kill before we start our own.
//
// binaryPath is the configured mtg path (e.g. "bin/mtg-linux-amd64"); matching
// is done on the executable's base name so it is independent of the bin folder
// and still works after an update has deleted the binary (the running process's
// /proc/<pid>/exe then reads as "<path> (deleted)", so argv[0] is used too).
func killStrayMtgProcesses(binaryPath string) int {
base := filepath.Base(binaryPath)
if base == "" || base == "." || base == string(filepath.Separator) {
return 0
}
self := os.Getpid()
entries, err := os.ReadDir("/proc")
if err != nil {
return 0
}
killed := 0
for _, e := range entries {
pid, err := strconv.Atoi(e.Name())
if err != nil || pid == self {
continue
}
if procExeBase(pid) != base && cmdlineArgv0Base(pid) != base {
continue
}
if err := syscall.Kill(pid, syscall.SIGKILL); err == nil {
killed++
}
}
return killed
}
// procExeBase returns the base name of /proc/<pid>/exe, or "" if unreadable.
func procExeBase(pid int) string {
exe, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid))
if err != nil {
return ""
}
return filepath.Base(exe)
}
// cmdlineArgv0Base returns the base name of argv[0] from /proc/<pid>/cmdline,
// the reliable fallback when the binary has been replaced or exe is unreadable.
func cmdlineArgv0Base(pid int) string {
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
if err != nil || len(data) == 0 {
return ""
}
argv0 := data
if i := strings.IndexByte(string(data), 0); i >= 0 {
argv0 = data[:i]
}
if len(argv0) == 0 {
return ""
}
return filepath.Base(string(argv0))
}