边缘网关 + Agent 的端口中继:Agent 主动连出到网关,把内网目标挂到网关端口上; 通过邀请链接按设备授权,可选审批,首次连接起计时,到期、闲置自动收回。 - portrelay-server:边缘网关(端口池、白名单与授权租约、邀请与授权网页) - portrelay-agent:Agent(yamux 隧道、本地 API 与网页面板、流量统计) - mac-native:SwiftUI 菜单栏 App
106 lines
2.5 KiB
Go
106 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"embed"
|
|
"flag"
|
|
"fmt"
|
|
"io/fs"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"runtime"
|
|
"syscall"
|
|
|
|
"github.com/DeepFal/PortRelay/client"
|
|
)
|
|
|
|
//go:embed all:web
|
|
var webFS embed.FS
|
|
|
|
// version 由发布构建通过 -ldflags "-X main.version=..." 注入
|
|
var version = "dev"
|
|
|
|
func main() {
|
|
httpPort := flag.Int("port", 33080, "Local dashboard HTTP port")
|
|
noBrowser := flag.Bool("no-browser", false, "Do not auto-open browser")
|
|
showVersion := flag.Bool("version", false, "Print version and exit")
|
|
flag.Parse()
|
|
|
|
if *showVersion {
|
|
fmt.Println("portrelay-agent", version)
|
|
return
|
|
}
|
|
|
|
log.Printf("==================================================")
|
|
log.Printf(" PortRelay Agent %s", version)
|
|
log.Printf(" Dashboard: http://127.0.0.1:%d", *httpPort)
|
|
log.Printf("==================================================")
|
|
|
|
// 先占住面板端口:已有实例在运行时直接退出,避免第二个 Agent 连上服务端顶替会话
|
|
addr := fmt.Sprintf("127.0.0.1:%d", *httpPort)
|
|
ln, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
log.Fatalf("Dashboard port %s unavailable (is another PortRelay agent running?): %v", addr, err)
|
|
}
|
|
|
|
cm, err := client.NewConfigManager()
|
|
if err != nil {
|
|
log.Fatalf("Failed to init config manager: %v", err)
|
|
}
|
|
|
|
agent := client.NewAgent(cm)
|
|
agent.Start()
|
|
|
|
// Strip 'web' prefix for embed fs
|
|
subFS, err := fs.Sub(webFS, "web")
|
|
if err != nil {
|
|
log.Fatalf("Failed to load embedded web assets: %v", err)
|
|
}
|
|
|
|
apiServer := client.NewAPIServer(cm, agent, http.FS(subFS))
|
|
apiServer.OnShutdown = func() {
|
|
log.Printf("Shutdown requested via API, stopping PortRelay Agent...")
|
|
agent.Stop()
|
|
_ = cm.Close()
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Graceful shutdown
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
go func() {
|
|
<-sigChan
|
|
log.Printf("\nShutting down PortRelay Agent...")
|
|
agent.Stop()
|
|
_ = cm.Close()
|
|
os.Exit(0)
|
|
}()
|
|
|
|
if !*noBrowser {
|
|
go openBrowser(fmt.Sprintf("http://127.0.0.1:%d", *httpPort))
|
|
}
|
|
|
|
log.Printf("Serving local web control panel at http://%s", addr)
|
|
if err := http.Serve(ln, apiServer.Handler()); err != nil {
|
|
agent.Stop()
|
|
_ = cm.Close()
|
|
log.Fatalf("HTTP server failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func openBrowser(url string) {
|
|
var cmd *exec.Cmd
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
cmd = exec.Command("open", url)
|
|
case "windows":
|
|
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
|
|
default:
|
|
cmd = exec.Command("xdg-open", url)
|
|
}
|
|
_ = cmd.Start()
|
|
}
|