增加自动扫描

This commit is contained in:
iluobei
2026-04-07 16:35:45 +08:00
parent bd3c03d51d
commit 1415cc7fe0
5 changed files with 323 additions and 2 deletions
+78 -2
View File
@@ -301,6 +301,9 @@ func (c *Client) connectAndRun(ctx context.Context) error {
log.Printf("[Agent] Failed to send initial heartbeat: %v", err) log.Printf("[Agent] Failed to send initial heartbeat: %v", err)
} }
// Send scan result to master for auto-sync
go c.sendScanResult(conn)
return c.runMessageLoop(ctx, conn) return c.runMessageLoop(ctx, conn)
} }
@@ -950,6 +953,7 @@ func (e *AuthError) IsTokenInvalid() bool {
const ( const (
WSMsgTypeCertDeploy = "cert_deploy" WSMsgTypeCertDeploy = "cert_deploy"
WSMsgTypeTokenUpdate = "token_update" WSMsgTypeTokenUpdate = "token_update"
WSMsgTypeScanResult = "scan_result"
) )
// WSCertDeployPayload represents a certificate deploy command from master // WSCertDeployPayload represents a certificate deploy command from master
@@ -1030,11 +1034,11 @@ func deployCert(certPEM, keyPEM, certPath, keyPath, reloadTarget string) error {
switch reloadTarget { switch reloadTarget {
case "nginx": case "nginx":
return runCmd("nginx", "-s", "reload") return reloadNginxCmd()
case "xray": case "xray":
return runCmd("systemctl", "restart", "xray") return runCmd("systemctl", "restart", "xray")
case "both": case "both":
if err := runCmd("nginx", "-s", "reload"); err != nil { if err := reloadNginxCmd(); err != nil {
return err return err
} }
return runCmd("systemctl", "restart", "xray") return runCmd("systemctl", "restart", "xray")
@@ -1042,6 +1046,15 @@ func deployCert(certPEM, keyPEM, certPath, keyPath, reloadTarget string) error {
return nil return nil
} }
func reloadNginxCmd() error {
for _, bin := range []string{"/usr/local/nginx/sbin/nginx", "nginx"} {
if path, err := exec.LookPath(bin); err == nil {
return runCmd(path, "-s", "reload")
}
}
return runCmd("systemctl", "reload", "nginx")
}
func runCmd(name string, args ...string) error { func runCmd(name string, args ...string) error {
if output, err := exec.Command(name, args...).CombinedOutput(); err != nil { if output, err := exec.Command(name, args...).CombinedOutput(); err != nil {
return fmt.Errorf("%s: %s: %w", name, string(output), err) return fmt.Errorf("%s: %s: %w", name, string(output), err)
@@ -1058,3 +1071,66 @@ func (c *Client) handleTokenUpdate(payload WSTokenUpdatePayload) {
log.Printf("[Agent] Token updated successfully in memory") log.Printf("[Agent] Token updated successfully in memory")
} }
// sendScanResult scans local xray status and sends results to master
func (c *Client) sendScanResult(conn *websocket.Conn) {
// Check xray running status
xrayRunning := false
xrayVersion := ""
cmd := exec.Command("xray", "version")
if out, err := cmd.Output(); err == nil {
xrayVersion = strings.TrimSpace(strings.Split(string(out), "\n")[0])
}
if exec.Command("systemctl", "is-active", "--quiet", "xray").Run() == nil {
xrayRunning = true
}
// Read inbounds from config
var inbounds []map[string]interface{}
configPaths := []string{
"/usr/local/etc/xray/config.json",
"/etc/xray/config.json",
}
for _, cfgPath := range configPaths {
data, err := os.ReadFile(cfgPath)
if err != nil {
continue
}
var config map[string]interface{}
if json.Unmarshal(data, &config) != nil {
continue
}
if ibs, ok := config["inbounds"].([]interface{}); ok {
for _, ib := range ibs {
if m, ok := ib.(map[string]interface{}); ok {
if tag, _ := m["tag"].(string); tag == "api" {
continue
}
inbounds = append(inbounds, m)
}
}
}
break
}
payload, _ := json.Marshal(map[string]interface{}{
"xray_running": xrayRunning,
"xray_version": xrayVersion,
"inbounds": inbounds,
})
msg := map[string]interface{}{
"type": WSMsgTypeScanResult,
"payload": json.RawMessage(payload),
}
c.wsMu.Lock()
err := conn.WriteJSON(msg)
c.wsMu.Unlock()
if err != nil {
log.Printf("[Agent] Failed to send scan_result: %v", err)
return
}
log.Printf("[Agent] Sent scan_result: xray_running=%v, inbounds=%d", xrayRunning, len(inbounds))
}
+6
View File
@@ -0,0 +1,6 @@
package handler
import _ "embed"
//go:embed xray_default_config.json
var defaultXrayConfig []byte
+31
View File
@@ -297,6 +297,9 @@ func (h *ManageHandler) HandleXrayInstall(w http.ResponseWriter, r *http.Request
log.Printf("[Manage] Xray installed successfully") log.Printf("[Manage] Xray installed successfully")
// Deploy default config if no config exists
h.deployDefaultXrayConfig()
writeJSON(w, http.StatusOK, map[string]interface{}{ writeJSON(w, http.StatusOK, map[string]interface{}{
"success": true, "success": true,
"message": "Xray installed successfully", "message": "Xray installed successfully",
@@ -2511,6 +2514,31 @@ func runCommand(name string, args ...string) error {
return nil return nil
} }
// deployDefaultXrayConfig deploys the embedded default xray config if no config exists.
func (h *ManageHandler) deployDefaultXrayConfig() {
configPath := "/usr/local/etc/xray/config.json"
if _, err := os.Stat(configPath); err == nil {
// Config already exists — run EnsureXrayConfig to add missing sections
result := h.EnsureXrayConfig()
if result.Modified {
log.Printf("[Manage] Xray config updated after install: added %v", result.AddedSections)
exec.Command("systemctl", "restart", "xray").Run()
}
return
}
if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
log.Printf("[Manage] Failed to create xray config dir: %v", err)
return
}
if err := os.WriteFile(configPath, defaultXrayConfig, 0644); err != nil {
log.Printf("[Manage] Failed to write default xray config: %v", err)
return
}
log.Printf("[Manage] Deployed default xray config to %s", configPath)
exec.Command("systemctl", "restart", "xray").Run()
}
// ================== SSE Streaming Install/Remove ================== // ================== SSE Streaming Install/Remove ==================
func sseStreamCmd(w http.ResponseWriter, r *http.Request, cmd *exec.Cmd, completeMsg string) { func sseStreamCmd(w http.ResponseWriter, r *http.Request, cmd *exec.Cmd, completeMsg string) {
@@ -2600,6 +2628,9 @@ func (h *ManageHandler) HandleXrayInstallStream(w http.ResponseWriter, r *http.R
`bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install`) `bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install`)
cmd.Env = os.Environ() cmd.Env = os.Environ()
sseStreamCmd(w, r, cmd, "Xray installed successfully") sseStreamCmd(w, r, cmd, "Xray installed successfully")
// Deploy default config after install
h.deployDefaultXrayConfig()
} }
func (h *ManageHandler) HandleXrayRemoveStream(w http.ResponseWriter, r *http.Request) { func (h *ManageHandler) HandleXrayRemoveStream(w http.ResponseWriter, r *http.Request) {
+104
View File
@@ -0,0 +1,104 @@
{
"log": {
"access": "/var/log/xray/access.log",
"error": "/var/log/xray/error.log",
"loglevel": "error"
},
"dns": {},
"api": {
"tag": "api",
"services": [
"HandlerService",
"LoggerService",
"StatsService",
"RoutingService"
]
},
"stats": {},
"policy": {
"levels": {
"0": {
"handshake": 5,
"connIdle": 300,
"uplinkOnly": 2,
"downlinkOnly": 2,
"statsUserUplink": true,
"statsUserDownlink": true
}
},
"system": {
"statsInboundUplink": true,
"statsInboundDownlink": true,
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": [
"api"
],
"outboundTag": "api"
},
{
"type": "field",
"protocol": [
"bittorrent"
],
"marktag": "ban_bt",
"outboundTag": "block"
},
{
"type": "field",
"ip": [
"geoip:cn"
],
"marktag": "ban_geoip_cn",
"outboundTag": "block"
},
{
"type": "field",
"domain": [
"geosite:openai"
],
"marktag": "fix_openai",
"outboundTag": "direct"
},
{
"type": "field",
"ip": [
"geoip:private"
],
"outboundTag": "block"
}
]
},
"inbounds": [
{
"tag": "api",
"port": 46736,
"listen": "127.0.0.1",
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom"
},
{
"tag": "block",
"protocol": "blackhole"
}
],
"metrics": {
"tag": "Metrics",
"listen": "127.0.0.1:38889"
}
}
+104
View File
@@ -0,0 +1,104 @@
{
"log": {
"access": "/var/log/xray/access.log",
"error": "/var/log/xray/error.log",
"loglevel": "error"
},
"dns": {},
"api": {
"tag": "api",
"services": [
"HandlerService",
"LoggerService",
"StatsService",
"RoutingService"
]
},
"stats": {},
"policy": {
"levels": {
"0": {
"handshake": 5,
"connIdle": 300,
"uplinkOnly": 2,
"downlinkOnly": 2,
"statsUserUplink": true,
"statsUserDownlink": true
}
},
"system": {
"statsInboundUplink": true,
"statsInboundDownlink": true,
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": [
"api"
],
"outboundTag": "api"
},
{
"type": "field",
"protocol": [
"bittorrent"
],
"marktag": "ban_bt",
"outboundTag": "block"
},
{
"type": "field",
"ip": [
"geoip:cn"
],
"marktag": "ban_geoip_cn",
"outboundTag": "block"
},
{
"type": "field",
"domain": [
"geosite:openai"
],
"marktag": "fix_openai",
"outboundTag": "direct"
},
{
"type": "field",
"ip": [
"geoip:private"
],
"outboundTag": "block"
}
]
},
"inbounds": [
{
"tag": "api",
"port": 46736,
"listen": "127.0.0.1",
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"tag": "direct",
"protocol": "freedom"
},
{
"tag": "block",
"protocol": "blackhole"
}
],
"metrics": {
"tag": "Metrics",
"listen": "127.0.0.1:38889"
}
}