From 096a8275be3ebd71371eb0dd062aa283b5085afc Mon Sep 17 00:00:00 2001 From: penguinehis Date: Wed, 27 May 2026 14:37:13 -0300 Subject: [PATCH] Fix go version --- go.mod | 2 +- install_bridge.sh | 66 +++++++++++++++++++++++++-- main.go | 113 +++++++++++++++++++++++----------------------- 3 files changed, 120 insertions(+), 61 deletions(-) diff --git a/go.mod b/go.mod index 8d6518f..5b627be 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module dragoncore-bridge -go 1.21 +go 1.13 diff --git a/install_bridge.sh b/install_bridge.sh index 095b835..a9d3c59 100644 --- a/install_bridge.sh +++ b/install_bridge.sh @@ -28,17 +28,75 @@ fi need_cmd() { command -v "$1" >/dev/null 2>&1; } rand_hex() { openssl rand -hex "$1"; } +# The bridge source is intentionally Go 1.13-compatible so old VPS images +# such as Ubuntu 20.04 can compile it with their distro package. +# If the distro Go is older than 1.13, install a known-good official Go. +GO_MIN_MAJOR=1 +GO_MIN_MINOR=13 +GO_VERSION="${GO_VERSION:-1.21.13}" +GO_BIN="" + +go_version_ok() { + if ! need_cmd go; then + return 1 + fi + ver="$(go version 2>/dev/null | awk '{print $3}' | sed 's/^go//; s/[^0-9.].*$//')" + major="$(printf '%s' "$ver" | cut -d. -f1)" + minor="$(printf '%s' "$ver" | cut -d. -f2)" + case "$major:$minor" in + *[!0-9:]*|:*) return 1 ;; + esac + if [ "$major" -gt "$GO_MIN_MAJOR" ]; then + return 0 + fi + if [ "$major" -eq "$GO_MIN_MAJOR" ] && [ "$minor" -ge "$GO_MIN_MINOR" ]; then + return 0 + fi + return 1 +} + +install_official_go() { + arch="$(uname -m)" + case "$arch" in + x86_64|amd64) goarch="amd64" ;; + aarch64|arm64) goarch="arm64" ;; + armv6l|armv7l) goarch="armv6l" ;; + i386|i686) goarch="386" ;; + *) echo "Unsupported CPU architecture for automatic Go install: $arch"; exit 1 ;; + esac + url="https://go.dev/dl/go${GO_VERSION}.linux-${goarch}.tar.gz" + tmp="/tmp/go${GO_VERSION}.linux-${goarch}.tar.gz" + echo "Installing Go ${GO_VERSION} from ${url} ..." + curl -fsSL "$url" -o "$tmp" + rm -rf /usr/local/go + tar -C /usr/local -xzf "$tmp" + ln -sf /usr/local/go/bin/go /usr/local/bin/go + ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt + GO_BIN="/usr/local/go/bin/go" +} + export DEBIAN_FRONTEND=noninteractive if need_cmd apt-get; then apt-get update -y apt-get install -y ca-certificates curl wget git openssl build-essential if ! need_cmd go; then - apt-get install -y golang-go + apt-get install -y golang-go || true fi elif need_cmd yum; then - yum install -y ca-certificates curl wget git openssl gcc make golang + yum install -y ca-certificates curl wget git openssl gcc make golang || yum install -y ca-certificates curl wget git openssl gcc make elif need_cmd dnf; then - dnf install -y ca-certificates curl wget git openssl gcc make golang + dnf install -y ca-certificates curl wget git openssl gcc make golang || dnf install -y ca-certificates curl wget git openssl gcc make +fi + +if go_version_ok; then + GO_BIN="$(command -v go)" +else + install_official_go +fi + +if ! "$GO_BIN" version >/dev/null 2>&1; then + echo "Go installation failed. Cannot build dragoncore-bridge." + exit 1 fi mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" @@ -56,7 +114,7 @@ else fi cd "$INSTALL_DIR/src" -go build -trimpath -ldflags "-s -w" -o "$BIN" . +"$GO_BIN" build -trimpath -ldflags "-s -w" -o "$BIN" . chmod 755 "$BIN" USER_NAME="admin" diff --git a/main.go b/main.go index 4a17f3e..75e1929 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "errors" "flag" "fmt" + "io/ioutil" "log" "net" "net/http" @@ -121,7 +122,7 @@ func getenv(k, def string) string { } func loadConfig(path string) (Config, error) { - b, err := os.ReadFile(path) + b, err := ioutil.ReadFile(path) if err != nil { return Config{}, err } @@ -134,7 +135,7 @@ func loadConfig(path string) (Config, error) { func loadStore(path string) (*Store, error) { st := &Store{path: path, Accounts: map[string]Account{}} - b, err := os.ReadFile(path) + b, err := ioutil.ReadFile(path) if errors.Is(err, os.ErrNotExist) { return st, nil } @@ -157,7 +158,7 @@ func loadStore(path string) (*Store, error) { func (s *Store) saveLocked() error { tmp := s.path + ".tmp" b, _ := json.MarshalIndent(s, "", " ") - if err := os.WriteFile(tmp, b, 0600); err != nil { + if err := ioutil.WriteFile(tmp, b, 0600); err != nil { return err } return os.Rename(tmp, s.path) @@ -165,7 +166,7 @@ func (s *Store) saveLocked() error { func randToken(n int) string { b := make([]byte, n); _, _ = rand.Read(b); return hex.EncodeToString(b) } -func writeJSON(w http.ResponseWriter, v any) { +func writeJSON(w http.ResponseWriter, v interface{}) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(v) } @@ -189,7 +190,7 @@ func (a *App) handleLogin(w http.ResponseWriter, r *http.Request) { a.sessMu.Lock() a.sessions[tok] = time.Now().Add(12 * time.Hour) a.sessMu.Unlock() - writeJSON(w, map[string]any{"token": tok, "username": a.cfg.Username, "role": "admin"}) + writeJSON(w, map[string]interface{}{"token": tok, "username": a.cfg.Username, "role": "admin"}) } func (a *App) auth(next http.Handler) http.Handler { @@ -215,7 +216,7 @@ func (a *App) auth(next http.Handler) http.Handler { } func (a *App) handleMe(w http.ResponseWriter, r *http.Request) { - writeJSON(w, map[string]any{"username": a.cfg.Username, "role": "admin"}) + writeJSON(w, map[string]interface{}{"username": a.cfg.Username, "role": "admin"}) } func (a *App) handleUsers(w http.ResponseWriter, r *http.Request) { @@ -303,7 +304,7 @@ func (a *App) handleDragonCreate(w http.ResponseWriter, r *http.Request) { errText(w, 400, err.Error()) return } - writeJSON(w, map[string]any{"ok": true}) + writeJSON(w, map[string]interface{}{"ok": true}) } func (a *App) handleDragonDelete(w http.ResponseWriter, r *http.Request) { @@ -322,7 +323,7 @@ func (a *App) handleDragonDelete(w http.ResponseWriter, r *http.Request) { errText(w, 400, err.Error()) return } - writeJSON(w, map[string]any{"ok": true}) + writeJSON(w, map[string]interface{}{"ok": true}) } func (a *App) handleDragonSync(w http.ResponseWriter, r *http.Request) { @@ -352,7 +353,7 @@ func (a *App) handleDragonSync(w http.ResponseWriter, r *http.Request) { ok++ } } - writeJSON(w, map[string]any{"ok": ok, "fail": fail}) + writeJSON(w, map[string]interface{}{"ok": ok, "fail": fail}) } func (a *App) createSSH(username, password string, limit int, expiresAt *time.Time, uuid string, withXray bool) error { @@ -437,7 +438,7 @@ func passwordHash(password string) (string, error) { func writeCompatUserFiles(username, password string, limit int) error { _ = os.MkdirAll("/etc/SSHPlus/senha", 0755) - _ = os.WriteFile("/etc/SSHPlus/senha/"+username, []byte(password+"\n"), 0600) + _ = ioutil.WriteFile("/etc/SSHPlus/senha/"+username, []byte(password+"\n"), 0600) upsertLine("/root/usuarios.db", username, fmt.Sprintf("%s %d", username, limit)) if _, err := os.Stat("/opt/DragonCore/menu.php"); err == nil { _ = exec.Command("php", "/opt/DragonCore/menu.php", "deleteData", username).Run() @@ -464,7 +465,7 @@ func upsertLine(path, prefix, line string) { } } func removeLinePrefix(path, prefix string) { - b, err := os.ReadFile(path) + b, err := ioutil.ReadFile(path) if err != nil { return } @@ -478,7 +479,7 @@ func removeLinePrefix(path, prefix string) { } out = append(out, l) } - _ = os.WriteFile(path, []byte(strings.Join(out, "\n")+"\n"), 0644) + _ = ioutil.WriteFile(path, []byte(strings.Join(out, "\n")+"\n"), 0644) } func (a *App) expiryLoop() { @@ -500,12 +501,12 @@ func (a *App) expiryLoop() { } } -func listSystemUsers(st *Store) []map[string]any { +func listSystemUsers(st *Store) []map[string]interface{} { st.mu.Lock() defer st.mu.Unlock() - out := make([]map[string]any, 0, len(st.Accounts)) + out := make([]map[string]interface{}, 0, len(st.Accounts)) for _, ac := range st.Accounts { - out = append(out, map[string]any{"username": ac.Username, "active_conns": activeProcCount(ac.Username), "max_connections": ac.MaxConnections, "expires_at": ac.ExpiresAt, "uuid": ac.UUID}) + out = append(out, map[string]interface{}{"username": ac.Username, "active_conns": activeProcCount(ac.Username), "max_connections": ac.MaxConnections, "expires_at": ac.ExpiresAt, "uuid": ac.UUID}) } sort.Slice(out, func(i, j int) bool { return fmt.Sprint(out[i]["username"]) < fmt.Sprint(out[j]["username"]) }) return out @@ -541,22 +542,22 @@ func parseTimeMaybe(s string) (*time.Time, error) { func xrayConfigPaths() []string { return []string{"/usr/local/etc/xray/config.json", "/etc/xray/config.json", "/etc/v2ray/config.json"} } -func readJSONFile(path string) (map[string]any, error) { - b, err := os.ReadFile(path) +func readJSONFile(path string) (map[string]interface{}, error) { + b, err := ioutil.ReadFile(path) if err != nil { return nil, err } - var m map[string]any + var m map[string]interface{} err = json.Unmarshal(b, &m) return m, err } -func writeJSONFile(path string, m map[string]any) error { +func writeJSONFile(path string, m map[string]interface{}) error { b, err := json.MarshalIndent(m, "", " ") if err != nil { return err } tmp := path + ".tmp" - if err := os.WriteFile(tmp, b, 0644); err != nil { + if err := ioutil.WriteFile(tmp, b, 0644); err != nil { return err } return os.Rename(tmp, path) @@ -572,28 +573,28 @@ func addXrayClientAll(uuid, email string) error { if err != nil { return err } - inb, _ := cfg["inbounds"].([]any) + inb, _ := cfg["inbounds"].([]interface{}) fileChanged := false for _, it := range inb { - im, ok := it.(map[string]any) + im, ok := it.(map[string]interface{}) if !ok || fmt.Sprint(im["protocol"]) != "vless" { continue } - settings, _ := im["settings"].(map[string]any) + settings, _ := im["settings"].(map[string]interface{}) if settings == nil { - settings = map[string]any{} + settings = map[string]interface{}{} im["settings"] = settings } - clients, _ := settings["clients"].([]any) + clients, _ := settings["clients"].([]interface{}) exists := false for _, c := range clients { - cm, _ := c.(map[string]any) + cm, _ := c.(map[string]interface{}) if fmt.Sprint(cm["id"]) == uuid || fmt.Sprint(cm["email"]) == email { exists = true } } if !exists { - settings["clients"] = append(clients, map[string]any{"id": uuid, "alterId": 0, "email": email}) + settings["clients"] = append(clients, map[string]interface{}{"id": uuid, "alterId": 0, "email": email}) fileChanged = true } } @@ -620,21 +621,21 @@ func removeXrayClientAll(uuid string) error { if err != nil { return err } - inb, _ := cfg["inbounds"].([]any) + inb, _ := cfg["inbounds"].([]interface{}) fileChanged := false for _, it := range inb { - im, ok := it.(map[string]any) + im, ok := it.(map[string]interface{}) if !ok || fmt.Sprint(im["protocol"]) != "vless" { continue } - settings, _ := im["settings"].(map[string]any) + settings, _ := im["settings"].(map[string]interface{}) if settings == nil { continue } - clients, _ := settings["clients"].([]any) - out := make([]any, 0, len(clients)) + clients, _ := settings["clients"].([]interface{}) + out := make([]interface{}, 0, len(clients)) for _, c := range clients { - cm, _ := c.(map[string]any) + cm, _ := c.(map[string]interface{}) if fmt.Sprint(cm["id"]) == uuid { fileChanged = true continue @@ -663,9 +664,9 @@ func restartXray() { func (a *App) handleXrayInbounds(w http.ResponseWriter, r *http.Request) { type Inb struct { - Tag string `json:"tag"` - Protocol string `json:"protocol"` - Clients []map[string]any `json:"clients"` + Tag string `json:"tag"` + Protocol string `json:"protocol"` + Clients []map[string]interface{} `json:"clients"` } var out []Inb for _, path := range xrayConfigPaths() { @@ -673,19 +674,19 @@ func (a *App) handleXrayInbounds(w http.ResponseWriter, r *http.Request) { if err != nil { continue } - inb, _ := cfg["inbounds"].([]any) + inb, _ := cfg["inbounds"].([]interface{}) for i, it := range inb { - im, _ := it.(map[string]any) + im, _ := it.(map[string]interface{}) proto := fmt.Sprint(im["protocol"]) tag := fmt.Sprint(im["tag"]) if tag == "" || tag == "" { tag = fmt.Sprintf("%s-%d", proto, i) } - settings, _ := im["settings"].(map[string]any) - clientsAny, _ := settings["clients"].([]any) - clients := []map[string]any{} + settings, _ := im["settings"].(map[string]interface{}) + clientsAny, _ := settings["clients"].([]interface{}) + clients := []map[string]interface{}{} for _, c := range clientsAny { - if cm, ok := c.(map[string]any); ok { + if cm, ok := c.(map[string]interface{}); ok { clients = append(clients, cm) } } @@ -736,7 +737,7 @@ func (a *App) handleStats(w http.ResponseWriter, r *http.Request) { writeJSON(w, type cpuSample struct{ idle, total uint64 } func readCPU() cpuSample { - b, _ := os.ReadFile("/proc/stat") + b, _ := ioutil.ReadFile("/proc/stat") fields := strings.Fields(strings.SplitN(string(b), "\n", 2)[0]) var vals []uint64 for _, f := range fields[1:] { @@ -753,7 +754,7 @@ func readCPU() cpuSample { } return cpuSample{idle, total} } -func collectStats() map[string]any { +func collectStats() map[string]interface{} { c1 := readCPU() time.Sleep(150 * time.Millisecond) c2 := readCPU() @@ -762,10 +763,10 @@ func collectStats() map[string]any { cpu = 100 * (1 - float64(c2.idle-c1.idle)/float64(c2.total-c1.total)) } mem := readMem() - return map[string]any{"cpu_percent": cpu, "mem_total_bytes": mem["total"], "mem_used_bytes": mem["used"], "mem_avail_bytes": mem["avail"], "mem_percent": mem["percent"], "interfaces": readNet()} + return map[string]interface{}{"cpu_percent": cpu, "mem_total_bytes": mem["total"], "mem_used_bytes": mem["used"], "mem_avail_bytes": mem["avail"], "mem_percent": mem["percent"], "interfaces": readNet()} } -func readMem() map[string]any { - b, _ := os.ReadFile("/proc/meminfo") +func readMem() map[string]interface{} { + b, _ := ioutil.ReadFile("/proc/meminfo") vals := map[string]uint64{} for _, l := range strings.Split(string(b), "\n") { f := strings.Fields(l) @@ -782,11 +783,11 @@ func readMem() map[string]any { used = total - avail pct = 100 * float64(used) / float64(total) } - return map[string]any{"total": total, "used": used, "avail": avail, "percent": pct} + return map[string]interface{}{"total": total, "used": used, "avail": avail, "percent": pct} } -func readNet() []map[string]any { - b, _ := os.ReadFile("/proc/net/dev") - var out []map[string]any +func readNet() []map[string]interface{} { + b, _ := ioutil.ReadFile("/proc/net/dev") + var out []map[string]interface{} for _, l := range strings.Split(string(b), "\n") { if !strings.Contains(l, ":") { continue @@ -800,7 +801,7 @@ func readNet() []map[string]any { if len(f) >= 16 { rx, _ := strconv.ParseUint(f[0], 10, 64) tx, _ := strconv.ParseUint(f[8], 10, 64) - out = append(out, map[string]any{"name": name, "rx_bytes": rx, "tx_bytes": tx}) + out = append(out, map[string]interface{}{"name": name, "rx_bytes": rx, "tx_bytes": tx}) } } return out @@ -808,20 +809,20 @@ func readNet() []map[string]any { func (a *App) handleReboot(w http.ResponseWriter, r *http.Request) { go exec.Command("shutdown", "-r", "+1", "DragonCore bridge reboot requested").Run() - writeJSON(w, map[string]any{"ok": true}) + writeJSON(w, map[string]interface{}{"ok": true}) } func (a *App) handleRestartSSH(w http.ResponseWriter, r *http.Request) { _ = exec.Command("systemctl", "restart", "ssh").Run() _ = exec.Command("systemctl", "restart", "sshd").Run() - writeJSON(w, map[string]any{"ok": true}) + writeJSON(w, map[string]interface{}{"ok": true}) } func (a *App) handleCleanup(w http.ResponseWriter, r *http.Request) { _ = exec.Command("sh", "-c", "apt-get clean 2>/dev/null; journalctl --vacuum-time=3d 2>/dev/null; rm -rf /tmp/* 2>/dev/null").Run() - writeJSON(w, map[string]any{"ok": true}) + writeJSON(w, map[string]interface{}{"ok": true}) } func (a *App) handleXrayFix(w http.ResponseWriter, r *http.Request) { restartXray() - writeJSON(w, map[string]any{"ok": true}) + writeJSON(w, map[string]interface{}{"ok": true}) } func (a *App) handleLegacyCommand(w http.ResponseWriter, r *http.Request) {