first commit
This commit is contained in:
35
mihomo/default.yaml
Normal file
35
mihomo/default.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
mixed-port: 7890
|
||||
allow-lan: true
|
||||
bind-address: "*"
|
||||
mode: rule
|
||||
log-level: info
|
||||
ipv6: true
|
||||
external-controller: 0.0.0.0:9090
|
||||
tcp-concurrent: true
|
||||
find-process-mode: "off"
|
||||
dns:
|
||||
enable: true
|
||||
ipv6: true
|
||||
listen: 0.0.0.0:53
|
||||
enhanced-mode: redir-host
|
||||
fake-ip-range: 198.18.0.1/16
|
||||
fake-ip-filter:
|
||||
- "*.lan"
|
||||
- "*.local"
|
||||
- "+.market.xiaomi.com"
|
||||
default-nameserver:
|
||||
- 223.5.5.5
|
||||
- 119.29.29.29
|
||||
nameserver:
|
||||
- https://doh.pub/dns-query
|
||||
- https://dns.alidns.com/dns-query
|
||||
fallback:
|
||||
- tls://8.8.8.8:853
|
||||
- tls://1.1.1.1:853
|
||||
proxies: []
|
||||
proxy-groups: []
|
||||
rules:
|
||||
- MATCH,DIRECT
|
||||
profile:
|
||||
store-selected: true
|
||||
store-fake-ip: true
|
||||
322
mihomo/mihomo.go
Normal file
322
mihomo/mihomo.go
Normal file
@@ -0,0 +1,322 @@
|
||||
package mihomo
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
process *os.Process
|
||||
running bool
|
||||
configDir string
|
||||
|
||||
logMu sync.Mutex
|
||||
logRing [500]string
|
||||
logLen int
|
||||
logPos int
|
||||
)
|
||||
|
||||
//go:embed default.yaml
|
||||
var defaultConfigFS embed.FS
|
||||
|
||||
func init() {
|
||||
configDir = defaultConfigDir()
|
||||
}
|
||||
|
||||
func defaultConfigDir() string {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return "/lib/mihomo"
|
||||
}
|
||||
if resolved, err := filepath.EvalSymlinks(exe); err == nil {
|
||||
exe = resolved
|
||||
}
|
||||
base := filepath.Dir(exe)
|
||||
if base == "/usr/bin" || base == "/usr/sbin" || base == "/sbin" || base == "/bin" {
|
||||
return "/lib/mihomo"
|
||||
}
|
||||
return filepath.Join(base, "mihomo")
|
||||
}
|
||||
|
||||
func SetConfigDir(dir string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
configDir = dir
|
||||
}
|
||||
|
||||
func ConfigDir() string {
|
||||
return configDir
|
||||
}
|
||||
|
||||
func DataDir() string {
|
||||
return filepath.Join(ConfigDir(), "data")
|
||||
}
|
||||
|
||||
func CoresDir() string {
|
||||
return filepath.Join(ConfigDir(), "cores")
|
||||
}
|
||||
|
||||
func ConfigPath() string {
|
||||
return filepath.Join(DataDir(), "config.yaml")
|
||||
}
|
||||
|
||||
func CorePath() string {
|
||||
arch := runtime.GOARCH
|
||||
switch arch {
|
||||
case "amd64":
|
||||
arch = "amd64"
|
||||
case "arm64":
|
||||
arch = "arm64"
|
||||
case "arm":
|
||||
arch = "armv7"
|
||||
default:
|
||||
arch = "amd64"
|
||||
}
|
||||
return filepath.Join(CoresDir(), fmt.Sprintf("mihomo-linux-%s", arch))
|
||||
}
|
||||
|
||||
func EnsureDefaultConfig() error {
|
||||
cfgPath := ConfigPath()
|
||||
if _, err := os.Stat(cfgPath); err == nil {
|
||||
return nil
|
||||
}
|
||||
if err := os.MkdirAll(DataDir(), 0755); err != nil {
|
||||
return fmt.Errorf("mkdir mihomo data dir: %w", err)
|
||||
}
|
||||
data, err := defaultConfigFS.ReadFile("default.yaml")
|
||||
if err != nil {
|
||||
return fmt.Errorf("read default config: %w", err)
|
||||
}
|
||||
tmp := cfgPath + ".tmp"
|
||||
if err := os.WriteFile(tmp, data, 0644); err != nil {
|
||||
return fmt.Errorf("write default config: %w", err)
|
||||
}
|
||||
if err := os.Rename(tmp, cfgPath); err != nil {
|
||||
return fmt.Errorf("rename default config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendLog(line string) {
|
||||
logMu.Lock()
|
||||
defer logMu.Unlock()
|
||||
logRing[logPos%len(logRing)] = line
|
||||
logPos++
|
||||
if logLen < len(logRing) {
|
||||
logLen++
|
||||
}
|
||||
}
|
||||
|
||||
func Logs() []string {
|
||||
logMu.Lock()
|
||||
defer logMu.Unlock()
|
||||
result := make([]string, 0, logLen)
|
||||
start := logPos - logLen
|
||||
for i := start; i < logPos; i++ {
|
||||
result = append(result, logRing[i%len(logRing)])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type lineWriter struct{}
|
||||
|
||||
func (lineWriter) Write(p []byte) (int, error) {
|
||||
start := 0
|
||||
for i, b := range p {
|
||||
if b == '\n' {
|
||||
line := string(p[start:i])
|
||||
if line != "" {
|
||||
appendLog(line)
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
if start < len(p) {
|
||||
line := string(p[start:])
|
||||
if line != "" {
|
||||
appendLog(line)
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func Status() map[string]interface{} {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
corePath := CorePath()
|
||||
_, err := os.Stat(corePath)
|
||||
coreExists := err == nil
|
||||
|
||||
_, cfgErr := os.Stat(ConfigPath())
|
||||
cfgExists := cfgErr == nil
|
||||
|
||||
status := map[string]interface{}{
|
||||
"running": running,
|
||||
"core_exists": coreExists,
|
||||
"core_path": corePath,
|
||||
"config_dir": DataDir(),
|
||||
"config_file": ConfigPath(),
|
||||
"config_exists": cfgExists,
|
||||
}
|
||||
|
||||
if running && process != nil {
|
||||
status["pid"] = process.Pid
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func Start() error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if running {
|
||||
return fmt.Errorf("mihomo is already running")
|
||||
}
|
||||
|
||||
corePath := CorePath()
|
||||
if _, err := os.Stat(corePath); err != nil {
|
||||
return fmt.Errorf("mihomo core not found at %s: %w", corePath, err)
|
||||
}
|
||||
|
||||
cfgPath := ConfigPath()
|
||||
if _, err := os.Stat(cfgPath); err != nil {
|
||||
return fmt.Errorf("mihomo config not found at %s: %w", cfgPath, err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(corePath, "-d", DataDir())
|
||||
lw := lineWriter{}
|
||||
w := io.MultiWriter(os.Stdout, lw)
|
||||
cmd.Stdout = w
|
||||
cmd.Stderr = io.MultiWriter(os.Stderr, lw)
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("start mihomo: %w", err)
|
||||
}
|
||||
|
||||
process = cmd.Process
|
||||
running = true
|
||||
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
mu.Lock()
|
||||
running = false
|
||||
process = nil
|
||||
mu.Unlock()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "mihomo process exited: %v\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if !running {
|
||||
return fmt.Errorf("mihomo exited immediately")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Stop() error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if !running || process == nil {
|
||||
running = false
|
||||
process = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := process.Signal(os.Interrupt); err != nil {
|
||||
_ = process.Kill()
|
||||
}
|
||||
running = false
|
||||
process = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func Restart() error {
|
||||
if err := Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
return Start()
|
||||
}
|
||||
|
||||
func IsRunning() bool {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return running
|
||||
}
|
||||
|
||||
func InstallCore(srcPath string) error {
|
||||
arch := runtime.GOARCH
|
||||
switch arch {
|
||||
case "amd64":
|
||||
arch = "amd64"
|
||||
case "arm64":
|
||||
arch = "arm64"
|
||||
case "arm":
|
||||
arch = "armv7"
|
||||
default:
|
||||
arch = "amd64"
|
||||
}
|
||||
dstPath := filepath.Join(CoresDir(), fmt.Sprintf("mihomo-linux-%s", arch))
|
||||
|
||||
if err := os.MkdirAll(CoresDir(), 0755); err != nil {
|
||||
return fmt.Errorf("mkdir cores: %w", err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(srcPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read core source: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(dstPath, data, 0755); err != nil {
|
||||
return fmt.Errorf("write core: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadConfig() (map[string]interface{}, error) {
|
||||
data, err := os.ReadFile(ConfigPath())
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("read mihomo config: %w", err)
|
||||
}
|
||||
var cfg map[string]interface{}
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("parse mihomo config: %w", err)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func SaveConfig(cfg map[string]interface{}) error {
|
||||
data, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal mihomo config: %w", err)
|
||||
}
|
||||
if err := os.MkdirAll(DataDir(), 0755); err != nil {
|
||||
return fmt.Errorf("mkdir mihomo data dir: %w", err)
|
||||
}
|
||||
tmp := ConfigPath() + ".tmp"
|
||||
if err := os.WriteFile(tmp, data, 0644); err != nil {
|
||||
return fmt.Errorf("write mihomo config: %w", err)
|
||||
}
|
||||
if err := os.Rename(tmp, ConfigPath()); err != nil {
|
||||
return fmt.Errorf("rename mihomo config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user