diff --git a/apps/m9zCtrlTty/build.bat b/apps/m9zCtrlTty/build.bat new file mode 100644 index 0000000..0121550 --- /dev/null +++ b/apps/m9zCtrlTty/build.bat @@ -0,0 +1,47 @@ +@echo off +setlocal enabledelayedexpansion + +REM 处理架构参数(第三个参数) +if "%3"=="" ( + set "goarch=amd64" + set "releasePath=x86_64" +) else if "%3"=="arm" ( + set "goarch=arm" + set "releasePath=arm" +) else if "%3"=="arm64" ( + set "goarch=arm64" + set "releasePath=arm64" +) else if "%3"=="loong64" ( + set "goarch=loong64" + set "releasePath=loong64" +) else ( + echo 目标 %3 无法编译 + exit /b 1 +) + +REM 处理操作系统参数(第二个参数) +if "%2"=="linux" ( + echo 交叉编译目标为linux+%goarch% + set "CGO_ENABLED=0" + set "GOOS=linux" + set "GOARCH=%goarch%" + go build -o "%~n1-!releasePath!" %1 +) else if "%2"=="windows" ( + echo 交叉编译目标为windows+%goarch% + set "CGO_ENABLED=0" + set "GOOS=windows" + set "GOARCH=%goarch%" + go build -o "%~n1-!releasePath!.exe" %1 +) else if "%2"=="darwin" ( + echo 交叉编译目标为maxos+%goarch% + set "CGO_ENABLED=0" + set "GOOS=darwin" + set "GOARCH=%goarch%" + go build -o "%~n1-!releasePath!" %1 +) else ( + echo 参数不正确: %2 + exit /b 1 +) + +echo 脚本执行结束... +endlocal \ No newline at end of file diff --git a/apps/m9zCtrlTty/build.sh b/apps/m9zCtrlTty/build.sh new file mode 100755 index 0000000..7beb43e --- /dev/null +++ b/apps/m9zCtrlTty/build.sh @@ -0,0 +1,51 @@ +case $3 in + "") + goarch="amd64" + releasePath="x86_64" + break + ;; + "arm") + goarch="arm" + releasePath="arm" + break + ;; + "arm64") + goarch="arm64" + releasePath="arm64" + break + ;; + "loong64") + goarch="loong64" + releasePath="loong64" + break + ;; + *) + echo '目标 ' $3 ' 无法编译' + exit + ;; +esac + +buildfile=$1 + + +case $2 in + "linux"|"l") echo '交叉编译目标为linux+'$goarch + echo "CGO_ENABLED=0 GOOS=linux GOARCH="$goarch" go build "$1 + CGO_ENABLED=0 GOOS=linux GOARCH=$goarch go build -o ./"${buildfile%%.*}-"$releasePath $1 + break + ;; + "windows"|"w") echo '交叉编译目标为windows+'$goarch + echo "CGO_ENABLED=0 GOOS=windows GOARCH="$goarch" go build "$1 + CGO_ENABLED=0 GOOS=windows GOARCH=$goarch go build -o ././"${buildfile%%.*}-"$releasePath".exe" $1 + break + ;; + "darwin") echo '交叉编译目标为maxos+'$goarch + echo "CGO_ENABLED=0 GOOS=darwin GOARCH="$goarch" go build "$1 + CGO_ENABLED=0 GOOS=darwin GOARCH=$goarch go build -o ./"${buildfile%%.*}-"$releasePath $1 + break + ;; + *) echo '参数不正确:'$goarch + break + ;; +esac + echo '脚本执行结束...' diff --git a/apps/m9zCtrlTty/config/config.json b/apps/m9zCtrlTty/config/config.json new file mode 100644 index 0000000..9c2b20e --- /dev/null +++ b/apps/m9zCtrlTty/config/config.json @@ -0,0 +1,55 @@ +{ + "server": { + "port": "8089", + "static": "./wwwroot" + }, + "logger": { + "path": "logs", + "file": "{Y-m-d}.log", + "prefix": "", + "level": "debug", + "timeFormat": "2006-01-02 15:04:05", + "ctxKeys": [], + "header": true, + "stdout": true, + "rotateSize": 0, + "rotateExpire": 0, + "rotateBackupLimit": 0, + "rotateBackupExpire": 0, + "rotateBackupCompress": 0, + "rotateCheckInterval": "1h", + "stdoutColorDisabled": false, + "writerColorEnable": false + }, + "tty": { + "serialPortAddress": "/dev/ttyS2", + "baudRate": "9600" + }, + "deviceInfo": { + "deviceId": "TGK", + "version": "1.0.2", + "meterAddr": "0x04" + }, + "MessageInterval": 5, + "Firefox": { + "DebPkgPath": "./firefox", + "DebPkgName": "firefox-deb.tar.gz", + "InstallShellPath": "./firefox/firefox_install.sh" + }, + "zap": { + "level": "debug", + "prefix": "", + "format": "console", + "director": "logs", + "encode-level": "CapitalLevelEncoder", + "stacktrace-key": "stacktrace", + "show-line": true, + "console-in-log": true, + "log-in-console": true, + "retention-day": 30, + "max-size": "10mb", + "compress": true, + "compress-level": 9, + "check-interval": "30m" + } +} diff --git a/apps/m9zCtrlTty/m9zCtrlTty.go b/apps/m9zCtrlTty/m9zCtrlTty.go new file mode 100644 index 0000000..50ad981 --- /dev/null +++ b/apps/m9zCtrlTty/m9zCtrlTty.go @@ -0,0 +1,296 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/towgo/towgo/lib/jsonrpc" + "github.com/towgo/towgo/lib/system" + "github.com/towgo/towgo/lib/www" + "go.uber.org/zap" + "tgk-touch/internal/core" + g "tgk-touch/internal/global" + "tgk-touch/internal/initialize" + licenseterminal "tgk-touch/internal/module/license_terminal" + "tgk-touch/internal/module/maincontrollerClient" + + "log" + "os" + "os/exec" + "os/user" + "path/filepath" + "strings" + "time" + + "github.com/towgo/towgo/errors/tcode" + "github.com/towgo/towgo/towgo" +) + +var ( + productNumber string = "LampServer" +) + +func main() { + log.SetFlags(log.Lshortfile | log.LstdFlags) + defer func() { + if exception := recover(); exception != nil { + if v, ok := exception.(error); ok && gerror.HasStack(v) { + zap.S().Error("err %+v \n", v) + } else { + zap.S().Error("recover exception %+v\n", gerror.NewCodef(tcode.CodeInternalPanic, "%+v", exception)) + } + } + }() + appInit() + start() + +} +func appInit() { + initialize.Init() + initLicense() + +} +func initLicense() { + licenseterminal.SetProductNumber(productNumber) + licenseterminal.AutoFristActive() + licenseterminal.RegExpirationCall(productNumber, func() { + log.Println("产品:" + productNumber + "许可证到期") + list := []string{ + "/account/login", + "/license/activecode/get", + "/license/activecode/online/request", + "/license/getActiveCredential", + "/license/active", + "/license/list", + "/license/accesscode/get", + } + jsonrpc.MethodLockAll(list...) + }) + licenseterminal.RegReNewCall(productNumber, func() { + log.Println("产品:" + productNumber + "许可证续存") + jsonrpc.MethodUnlockAll() + }) +} +func start() { + var err error + var serialPortAddress string = g.Config().Tty.SerialPortAddress + if serialPortAddress == "" { + panic(gerror.Wrap(err, "串口地址未配置 tty.serialPortAddress")) + } + var baudRateint int = g.Config().Tty.BaudRate + if baudRateint == 0 { + zap.S().Info("波特率未配置 tty.baudRate , 使用 默认 9600") + baudRateint = 9600 + } + towgo.SetFunc("/getMessageInterval", getMessageInterval) + err = maincontrollerClient.UseSerialPort(serialPortAddress, baudRateint) + if err != nil { + panic(err) + } + webServer := www.WebServer{} + webServer.Wwwroot = system.GetPathOfProgram() + "wwwroot" + webServer.Index = []string{"index.html"} + g.HttpServerMux().HandleFunc("/", + webServer.WebServerHandller, + ) + g.HttpServerMux().HandleFunc("/jsonrpc/websocket", + towgo.DefaultWebSocketServer.WebsocketServiceHandller.ServeHTTP, + ) + g.HttpServerMux().HandleFunc("/jsonrpc", + towgo.HttpHandller) + towgo.DefaultExec = func(rpcConn towgo.JsonRpcConnection) { + if exception := recover(); exception != nil { + var msg string + if v, ok := exception.(error); ok && gerror.HasStack(v) { + g.Log().Error("towgo jsonrpc exception \n", v) + msg = v.Error() + } else { + g.Log().Error("towgo jsonrpc recover exception \n", gerror.NewCodef(tcode.CodeInternalPanic, "%+v", exception)) + msg = v.Error() + } + + rpcConn.WriteError(500, rpcConn.GetRpcRequest().Method+":"+msg) + } + } + install() + go runClient(g.Config().Server.Port) + core.RunServer() +} + +func getMessageInterval(rpcConn towgo.JsonRpcConnection) { + rpcConn.WriteResult(g.Config().MessageInterval) +} +func install() { + // 1. 检查并安装Firefox + if !isFirefoxInstalled() { + fmt.Println("Firefox未安装,正在安装...") + if err := installFirefox(); err != nil { + fmt.Printf("安装Firefox失败: %v\n", err) + os.Exit(1) + } + fmt.Println("Firefox安装成功") + } else { + fmt.Println("Firefox已安装") + } + + // 2. 设置当前应用开机自启动 + appPath, err := os.Executable() + if err != nil { + fmt.Printf("读取应用路径失败: %v\n", err) + os.Exit(1) + } + + if !isAutoStartEnabled(appPath) { + fmt.Println("正在设置开机自启动...") + if err := enableAutoStart(appPath); err != nil { + fmt.Printf("设置开机自启动失败: %v\n", err) + os.Exit(1) + } + fmt.Println("开机自启动设置成功") + } else { + fmt.Println("已设置开机自启动") + } +} + +func runClient(port int) { + // 配置显示器和Xauthority路径 + display := ":0" + currentUser, err := user.Current() + if err != nil { + fmt.Printf("读取当前用户失败: %v\n", err) + os.Exit(1) + } + xauthPath := filepath.Join(currentUser.HomeDir, ".Xauthority") + + for { + // 设置环境变量 + os.Setenv("DISPLAY", display) + os.Setenv("XAUTHORITY", xauthPath) + // 关键:这组参数 = 完全关闭密码管理器 + 禁止保存/填充/弹窗 + args := []string{ + /*"--kiosk", + "--noerrdialogs", + "--no-first-run", + "--new-instance", + // 👇 下面这 3 个就是禁密码弹窗的核心 + "--disable-features=PasswordManager", + "--pref=signon.rememberSignons=false", + "--pref=signon.autofillForms=false",*/ + + "http://127.0.0.1:" + fmt.Sprintf("%d", port), + } + // 启动Firefox + cmd := exec.Command("firefox", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + g.Log().Error(err.Error()) + time.Sleep(5 * time.Second) // 等待后重试 + continue + } + + } +} + +// 检查Firefox是否安装 +func isFirefoxInstalled() bool { + cmd := exec.Command("which", "firefox") + return cmd.Run() == nil +} + +// 安装Firefox +func installFirefox() error { + var p struct { + DebPkgPath string `json:"DebPkgPath"` + DebPkgName string `json:"DebPkgName"` + InstallShellPath string `json:"InstallShellPath"` + } + p.DebPkgPath = g.Config().Firefox.DebPkgPath + p.DebPkgName = g.Config().Firefox.DebPkgName + p.InstallShellPath = g.Config().Firefox.InstallShellPath + + command := exec.Command("sudo", "sh", "-c", fmt.Sprintf(` +echo "=== 当前工作目录 ===" +pwd +echo "" + +echo "=== 卸载旧版 Firefox ===" +dpkg -P firefox || echo "警告:卸载旧版 Firefox 失败(可能未安装)" +echo "" + +echo "=== 清理残留文件 ===" +rm -rf /opt/firefox* 2>/dev/null +echo "" + +echo "=== 开始安装 Firefox ===" +echo "1. 复制安装包到 /opt 目录..." +cp -v %s%s/%s /opt/firefox-deb.tar.gz || { echo "错误:复制安装包失败"; exit 1; } +echo "" + +echo "2. 解压安装包..." +cd /opt && tar -xzvf firefox-deb.tar.gz || { echo "错误:解压失败"; exit 1; } +echo "" + +echo "3. 安装依赖和主程序..." +cd /opt/firefox-deb && sudo dpkg -i *.deb || { echo "错误:安装 deb 包失败"; } +echo "" + +echo "4. 验证安装..." +firefox --version || { echo "错误:Firefox 未正确安装"; exit 1; } +echo "" + +echo "=== Firefox 安装完成 ===" +`, system.GetPathOfProgram(), p.DebPkgPath, p.DebPkgName)) + // 将命令的 stdout/stderr 直接绑定到当前终端 + command.Stdout = os.Stdout + command.Stderr = os.Stderr + + return command.Run() +} +func isAutoStartEnabled(appPath string) bool { + desktopFile := fmt.Sprintf("/etc/systemd/system/%s.service", strings.TrimSuffix(filepath.Base(appPath), filepath.Ext(appPath))) + + _, err := os.Stat(desktopFile) + return !os.IsNotExist(err) +} +func enableAutoStart(appPath string) error { + appName := strings.TrimSuffix(filepath.Base(appPath), filepath.Ext(appPath)) + serviceFile := fmt.Sprintf("/etc/systemd/system/%s.service", appName) + + // 创建systemd服务文件内容 + content := fmt.Sprintf(`[Unit] +Description=%s Background Service +After=network.target + +[Service] +Type=simple +ExecStart=%s +Restart=on-failure +RestartSec=10 +User=root +WorkingDirectory=%s + +[Install] +WantedBy=multi-user.target +`, + appName, + appPath, + filepath.Dir(appPath)) + + // 写入服务文件 + cmd := exec.Command("sudo", "bash", "-c", + fmt.Sprintf("echo '%s' > %s", content, serviceFile)) + if err := cmd.Run(); err != nil { + return err + } + + // 重新加载systemd配置 + cmd = exec.Command("sudo", "systemctl", "daemon-reload") + if err := cmd.Run(); err != nil { + return err + } + + // 启用服务 + cmd = exec.Command("sudo", "systemctl", "enable", appName+".service") + return cmd.Run() +}