godo 10 months ago
parent
commit
86dc81a78c
  1. 6
      CHANGELOG.md
  2. 31
      README.en.md
  3. 35
      README.md
  4. 29
      build.linux.sh
  5. 4
      docs/Store.md
  6. 13
      frontend/package-lock.json
  7. BIN
      frontend/public/image/bg/bg6.jpg
  8. BIN
      frontend/public/image/bg/bg7.jpg
  9. BIN
      frontend/public/image/bg/bg8.jpg
  10. BIN
      frontend/public/image/bg/bg9.jpg
  11. 28
      frontend/src/components/desktop/DeskItem.vue
  12. 3
      frontend/src/components/setting/ColorPicker.vue
  13. 58
      frontend/src/components/setting/SetCustom.vue
  14. 28
      frontend/src/components/store/Store.vue
  15. 3
      frontend/src/system/config.ts
  16. 1100
      godo/deps/darwin/brew-install.sh
  17. 2
      godo/go.mod
  18. 2
      godo/go.sum
  19. 68
      godo/store/cmds.go
  20. 25
      godo/store/install.go
  21. 153
      godo/store/os.go
  22. 91
      godo/store/port.go
  23. 8
      godo/store/store.go

6
CHANGELOG.md

@ -1 +1,5 @@
## 变更记录
## 变更记录
- 2024-08-02
1. 修复桌面更换背景文字颜色问题
2. 修复安装插件没有配置binPath的问题
3. 新增linux环境下获取安装命令的方法

31
README.en.md

@ -13,31 +13,34 @@ English | [简体中文](README.md)
[FAQ](./docs/Faq.md) | [API](./docs/Api.md) | [AppStore](./docs/Store.en.md)
### Install
</div>
[![Windows][Windows-image]][Windows-url]
[![MacOS][MacOS-image]][MacOS-url]
[![Linux][Linux-image]][Linux-url]
### 👇 Download and Install
[license-image]: ./docs/img/license_%20MIT.svg
1. ![Download for MacOS][Windows-image] **Windows User**:
- Windows (AMD64) [**gitee Download**](https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-amd64-installer.exe) | [**github Download**](https://github.com/phpk/godoos/releases/download/v1.0.0/godoos-amd64-installer.exe)
[license-url]: https://spdx.org/licenses/MIT.html
- Windows (ARM64) [**gitee Download**](https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-arm64-installer.exe) | [**github Download**](https://github.com/phpk/godoos/releases/download/v1.0.0/godoos-arm64-installer.exe)
[Windows-image]: ./docs/img/Windows.svg
2. ![Download for MacOS][MacOS-image] **MacOS User**:
[Windows-url]: https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-windows.exe
- MacOS (AMD64) [**gitee Download**](https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-macos-amd64.zip) | [**github Download**](https://github.com/phpk/godoos/releases/download/v1.0.0/godoos-macos-amd64.zip)
[MacOS-image]: ./docs/img/MacOS.svg
[MacOS-url]: https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-macos-amd64.zip
- MacOS (ARM64) [**gitee Download**](https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-macos-arm64.zip) | [**github Download**](https://github.com/phpk/godoos/releases/download/v1.0.0/godoos-macos-arm64.zip)
[Linux-image]: ./docs/img/Linux.svg
3. ![Download for Linux][Linux-image] **Linux User**:
[Linux-url]: https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-linux-amd64.zip
- Linux (AMD64) [**gitee Download**](https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-linux-amd64.zip) | [**github Download**](https://github.com/phpk/godoos/releases/download/v1.0.0/godoos-macos-arm64.zip)
</div>
<!-- 图标和链接 -->
[Windows-image]: ./docs/img/Windows.svg
[MacOS-image]: ./docs/img/MacOS.svg
[Linux-image]: ./docs/img/Linux.svg
[license-image]: ./docs/img/license_%20MIT.svg
[license-url]: https://spdx.org/licenses/MIT.html
## Highlights
- ***No need for internet connection, fully open source***

35
README.md

@ -13,32 +13,35 @@
[FAQ](./docs/Faq.md) | [API](./docs/Api.md) | [应用开发](./docs/Store.md)
</div>
### 📚 安装
### 👇 下载安装
[![Windows][Windows-image]][Windows-url]
[![MacOS][MacOS-image]][MacOS-url]
[![Linux][Linux-image]][Linux-url]
1. ![Download for MacOS][Windows-image] **Windows 用户**:
- Windows (AMD64) [**国内下载**](https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-amd64-installer.exe) | [**github下载**](https://github.com/phpk/godoos/releases/download/v1.0.0/godoos-amd64-installer.exe)
[license-image]: ./docs/img/license_%20MIT.svg
- Windows (ARM64) [**国内下载**](https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-arm64-installer.exe) | [**github下载**](https://github.com/phpk/godoos/releases/download/v1.0.0/godoos-arm64-installer.exe)
[license-url]: https://spdx.org/licenses/MIT.html
2. ![Download for MacOS][MacOS-image] **MacOS 用户**:
[Windows-image]: ./docs/img/Windows.svg
- MacOS (AMD64) [**国内下载**](https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-macos-amd64.zip) | [**github下载**](https://github.com/phpk/godoos/releases/download/v1.0.0/godoos-macos-amd64.zip)
[Windows-url]: https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-amd64-installer.exe
- MacOS (ARM64) [**国内下载**](https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-macos-arm64.zip) | [**github下载**](https://github.com/phpk/godoos/releases/download/v1.0.0/godoos-macos-arm64.zip)
[MacOS-image]: ./docs/img/MacOS.svg
[MacOS-url]: https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-macos-amd64.zip
3. ![Download for Linux][Linux-image] **Linux 用户**:
[Linux-image]: ./docs/img/Linux.svg
- Linux (AMD64) [**国内下载**](https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-linux-amd64.zip) | [**github下载**](https://github.com/phpk/godoos/releases/download/v1.0.0/godoos-macos-arm64.zip)
[Linux-url]: https://gitee.com/ruitao_admin/godoos/releases/download/v1.0.0/godoos-linux-amd64.zip
</div>
<!-- 图标和链接 -->
[Windows-image]: ./docs/img/Windows.svg
[MacOS-image]: ./docs/img/MacOS.svg
[Linux-image]: ./docs/img/Linux.svg
[license-image]: ./docs/img/license_%20MIT.svg
[license-url]: https://spdx.org/licenses/MIT.html
## 💝 亮点
- ***无需联网使用,全开源***
@ -50,6 +53,10 @@
- ***支持多平台,Windows、Linux、MacOS***
- ***完善的应用商店体系,简单学习一下[应用商店配置](./docs/Store.md)即可开发出复杂的应用***
## 💖 开源地址
- [Gitee](https://gitee.com/ruitao_admin/godoos)
- [Github](https://github.com/phpk/godoos)
## 🚀 演示视频
- [全程操作](https://www.bilibili.com/video/BV1NdvaeEEz3/?vd_source=739e0e59aeefdb2e9f760e5037d00245)

29
build.linux.sh

@ -1,4 +1,25 @@
wails build -platform linux/amd64 -s -o godoos-linux-amd64
wails build -platform linux/arm64 -s -o godoos-linux-arm64
wails build -platform darwin/amd64 -s -o godoos-darwin-amd64
wails build -platform darwin/arm64 -s -o godoos-darwin-arm64
#!/bin/bash
# 定义一个函数来执行压缩操作
compress_to_zip() {
local target_file="./build/bin/godoos$2"
local zip_name="./build/bin/$1.zip"
# 检查ZIP文件是否已经存在,如果存在则跳过压缩
if [ ! -f "$zip_name" ]; then
echo "Compressing $target_file to $zip_name"
zip -j "$zip_name" "$target_file"
else
echo "$zip_name already exists. Skipping compression."
fi
}
# 构建和压缩
wails build -platform linux/amd64 -s
compress_to_zip "godoos-linux-amd64" ""
wails build -platform linux/arm64 -s
compress_to_zip "godoos-linux-arm64" ""
wails build -platform darwin/amd64 -s
compress_to_zip "godoos-darwin-amd64" ".app"
wails build -platform darwin/arm64 -s
compress_to_zip "godoos-darwin-arm64" ".app"

4
docs/Store.md

@ -220,7 +220,7 @@ const comp = await fetch('http://localhost:56780/store/setting', {
2. `static`目录下的在执行install时文件会自动复制到`.godoos/static/`应用目录下
3. `store.json`如果设置了`icon`,并且`static`目录下存在该文件,则应用图标为该文件。否则为`install.json`中的icon
### 内置应用说明
### `commands`内置函数说明
- 系统封装了一些用于处理进程控制和文件操作的功能函数,以下是各函数的详细描述:
1. `start` 启动应用
2. `stop` 停止应用
@ -235,6 +235,8 @@ const comp = await fetch('http://localhost:56780/store/setting', {
11. `startApp` 启动其他应用,content为应用名
12. `stopApp` 停止其他应用,content为应用名
- 保留命令`uninstall`,如果设置了`uninstall`,系统在卸载的时候将会执行它
### 进阶操作
1. 下载[mysql8.0](https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.39-winx64.zip)
2. 参考demo下的mysql8.0目录,尝试自己制作安装包

13
frontend/package-lock.json

@ -1262,17 +1262,6 @@
"integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==",
"dev": true
},
"node_modules/core-js": {
"version": "3.37.1",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.37.1.tgz",
"integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==",
"hasInstallScript": true,
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
@ -2191,7 +2180,7 @@
"version": "5.5.4",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"devOptional": true,
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

BIN
frontend/public/image/bg/bg6.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 KiB

After

Width:  |  Height:  |  Size: 209 KiB

BIN
frontend/public/image/bg/bg7.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
frontend/public/image/bg/bg8.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

BIN
frontend/public/image/bg/bg9.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

28
frontend/src/components/desktop/DeskItem.vue

@ -1,15 +1,19 @@
<template>
<div class="desk-group">
<FileList :on-chosen="props.onChosen" :on-open="openapp" :file-list="appList"></FileList>
<FileList
:on-chosen="props.onChosen"
:on-open="openapp"
:file-list="appList"
></FileList>
</div>
</template>
<script lang="ts" setup>
import { mountEvent } from '@/system/event';
import { useSystem } from '@/system/index.ts';
import { useAppOpen } from '@/hook/useAppOpen';
import { onMounted } from 'vue';
import { mountEvent } from "@/system/event";
import { useSystem } from "@/system/index.ts";
import { useAppOpen } from "@/hook/useAppOpen";
import { onMounted } from "vue";
const { openapp, appList } = useAppOpen('apps');
const { openapp, appList } = useAppOpen("apps");
const props = defineProps({
onChosen: {
type: Function,
@ -17,7 +21,7 @@ const props = defineProps({
},
});
onMounted(() => {
mountEvent('file.props.edit', async () => {
mountEvent("file.props.edit", async () => {
useSystem().initAppList();
});
});
@ -28,5 +32,15 @@ onMounted(() => {
flex-direction: column;
flex-wrap: wrap;
height: 100%;
//
color: transparent; /* 文字颜色设为透明 */
text-shadow: 0 0 0.5px white, 0 0 1px black, 0 0 2px rgba(0, 0, 0, 2); /* 多层阴影 */
//
> * {
color: inherit; /* 继承颜色 */
text-shadow: inherit; /* 继承描边效果 */
font-size: 0.8rem;
}
}
</style>

3
frontend/src/components/setting/ColorPicker.vue

@ -39,6 +39,3 @@ const predefineColors = ref([
"#c7158577",
]);
</script>
<!-- <style>
@import url('vue-accessible-color-picker/styles');
</style> -->

58
frontend/src/components/setting/SetCustom.vue

@ -29,7 +29,7 @@
<template v-if="config.background.type === 'color'">
<div class="setting-item">
<label> </label>
<ColorPicker v-model="config.background.color"></ColorPicker>
<ColorPicker v-model:modelValue="config.background.color" @update:modelValue="onColorChange"></ColorPicker>
</div>
</template>
<template v-if="config.background.type === 'image'">
@ -39,7 +39,7 @@
v-for="(item, index) in config.background.imageList"
:key="index"
:class="config.background.url === item ? 'selected' : ''"
@click="config.background.url = item"
@click="setBg(item)"
>
<img :src="item" />
</li>
@ -49,21 +49,14 @@
<label> </label>
</div>
</template>
<div class="setting-item">
<label> </label>
<el-button @click="submit" type="primary">
{{ t("confirm") }}
</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { getSystemConfig, setSystemConfig } from "@/system/config";
import { ref, toRaw } from "vue";
import { Dialog, t, useSystem } from "@/system";
import { ref } from "vue";
import { t, useSystem } from "@/system";
const sys = useSystem();
const items = [t("background")];
const desktopOptions = [
@ -78,42 +71,23 @@ const desktopOptions = [
];
const activeIndex = ref(0);
const config = ref(getSystemConfig());
const config:any = ref(getSystemConfig());
function setBg(item: any){
config.value.background.url = item
config.value.background.type = "image";
setSystemConfig(config.value);
sys.initBackground();
/** 提交背景设置 */
async function submit() {
const val = toRaw(config.value);
setSystemConfig(val);
config.value = val;
Dialog.showMessageBox({
message: t("save.success"),
title: t("wallpaper"),
type: "success",
}).then(() => {
//location.reload();
sys.initBackground();
});
}
function onColorChange(color : string){
config.value.background.color = color;
config.value.background.type = "color";
setSystemConfig(config.value);
sys.initBackground();
}
const selectItem = (index: number) => {
activeIndex.value = index;
};
// async function submitStyle() {
// let rootStyle = system.getConfig("rootStyle");
// rootStyle = {
// ...rootStyle,
// "--icon-title-color": textColor.value,
// "--window-border-radius": winRadius.value,
// "--theme-main-color": taskBarColor.value,
// };
// await system.setConfig("rootStyle", rootStyle);
// Dialog.showMessageBox({
// message: t("save.success"),
// title: t("style"),
// type: "info",
// });
// }
</script>
<style scoped>

28
frontend/src/components/store/Store.vue

@ -28,7 +28,8 @@ async function install(item: any) {
}
const url = store.apiUrl + '/store/download'
const res = await download(item, url, postData)
console.log(res)
//console.log(res)
item.progress = 0
if(res.code < 0){
if(res.data && res.data.dependencies && res.data.dependencies.length > 0){
const names:any = []
@ -48,27 +49,6 @@ async function install(item: any) {
await store.checkProgress()
notifySuccess(t("store.installSuccess"))
}
// if (item.needInstall) {
// item.progress = 0
// const completion = await fetch(store.apiUrl + '/store/install?name=' + item.name)
// if (!completion.ok) {
// notifyError(t("store.installError"))
// return
// }
// const res = await completion.json()
// if (res.code && res.code < 0) {
// notifyError(res.message)
// return
// }
// if (res.data) {
// item.icon = res.data.icon
// }
// }
// await store.addDesktop(item);
// await store.checkProgress()
// notifySuccess(t("store.installSuccess"))
}
async function uninstall(item: any) {
@ -125,11 +105,11 @@ async function pauseApp(item: any) {
if (!res.ok) {
const msg = await res.text()
notifyError(msg)
return
//return
}
setTimeout(async () => {
await store.checkProgress()
}, 1000)
}, 3000)
}
async function restartApp(item: any) {

3
frontend/src/system/config.ts

@ -75,6 +75,9 @@ export const getSystemConfig = (ifset = false) => {
'/image/bg/bg4.jpg',
'/image/bg/bg5.jpg',
'/image/bg/bg6.jpg',
'/image/bg/bg7.jpg',
'/image/bg/bg8.jpg',
'/image/bg/bg9.jpg',
]
}
}

1100
godo/deps/darwin/brew-install.sh

File diff suppressed because it is too large

2
godo/go.mod

@ -3,6 +3,7 @@ module godo
go 1.22.5
require (
github.com/cavaliergopher/grab/v3 v3.0.1
github.com/fsnotify/fsnotify v1.7.0
github.com/gorilla/mux v1.8.1
github.com/minio/selfupdate v0.6.0
@ -11,7 +12,6 @@ require (
require (
aead.dev/minisign v0.2.0 // indirect
github.com/cavaliergopher/grab/v3 v3.0.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect

2
godo/go.sum

@ -37,8 +37,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=

68
godo/store/cmds.go

@ -30,22 +30,12 @@ func runStart(storeInfo StoreInfo) error {
}
}
binPath := storeInfo.Setting.BinPath
if !libs.PathExists(binPath) {
return fmt.Errorf("script file %s does not exist", storeInfo.Setting.BinPath)
}
var cmd *exec.Cmd
if len(storeInfo.Start.StartCmds) > 0 {
cmd = exec.Command(binPath, storeInfo.Start.StartCmds...)
} else {
cmd = exec.Command(binPath)
}
if runtime.GOOS == "windows" {
// 在Windows上,通过设置CreationFlags来隐藏窗口
cmd = SetHideConsoleCursor(cmd)
cmd, err := GetRunCmd(storeInfo.Setting.BinPath, storeInfo.Start.StartCmds)
if err != nil {
return fmt.Errorf("failed to get run cmd: %w", err)
}
if err := cmd.Start(); err != nil {
//log.Println("Error starting script:", err)
log.Printf("Error starting script: %v\nDetailed error: %v", err, cmd.Stderr)
return fmt.Errorf("error starting script: %v", err)
}
@ -69,9 +59,48 @@ func runStart(storeInfo StoreInfo) error {
}
return nil
}
func GetRunCmd(binPath string, cmds []string) (*exec.Cmd, error) {
var cmd *exec.Cmd
log.Printf("run script: %s", strings.Join(cmds, " "))
if binPath != "" {
if !libs.PathExists(binPath) {
return cmd, fmt.Errorf("script file %s does not exist", binPath)
}
// 如果是非Windows环境,设置可执行权限
if runtime.GOOS != "windows" {
// 设置文件权限,0755是一个常见的可执行文件权限掩码
if err := os.Chmod(binPath, 0755); err != nil {
return cmd, fmt.Errorf("failed to set executable permissions on %s: %w", binPath, err)
}
}
if len(cmds) > 0 {
cmd = exec.Command(binPath, cmds...)
} else {
cmd = exec.Command(binPath)
}
} else {
if len(cmds) == 0 {
return cmd, fmt.Errorf("no commands provided")
}
log.Printf("run script: %s", strings.Join(cmds, " "))
if len(cmds) > 1 {
cmd = exec.Command(cmds[0], cmds[1:]...)
} else {
cmd = exec.Command(cmds[0])
}
}
if runtime.GOOS == "windows" {
// 在Windows上,通过设置CreationFlags来隐藏窗口
cmd = SetHideConsoleCursor(cmd)
}
return cmd, nil
}
func RunStartApp(appName string) error {
return ExecuteScript(appName)
}
func RunStopApp(appName string) error {
return StopCmd(appName)
}
@ -87,13 +116,12 @@ func runExec(storeInfo StoreInfo, cmdParam Cmd) error {
if err != nil {
return fmt.Errorf("failed to set start environment variable %s: %w", storeInfo.Name, err)
}
log.Printf("bin path:%v", cmdParam.BinPath)
log.Printf("cmds:%v", cmdParam.Cmds)
cmd := exec.Command(cmdParam.BinPath, cmdParam.Cmds...)
if runtime.GOOS == "windows" {
// 在Windows上,通过设置CreationFlags来隐藏窗口
cmd = SetHideConsoleCursor(cmd)
cmd, err := GetRunCmd(cmdParam.BinPath, cmdParam.Cmds)
if err != nil {
return fmt.Errorf("failed to get run cmd: %w", err)
}
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to run exec process %s: %w", storeInfo.Name, err)
}

25
godo/store/install.go

@ -168,6 +168,15 @@ func Uninstallation(pluginName string) error {
return fmt.Errorf("the app is being used by other applications: %v", hasDeps)
}
}
storeInfo, err := GetStoreInfo(pluginName)
if err == nil {
if _, ok := storeInfo.Commands["uninstall"]; ok {
replacePlaceholdersInCmds(&storeInfo)
if err := RunCmds(storeInfo, "uninstall"); err != nil {
return fmt.Errorf("error running uninstall command: %w", err)
}
}
}
// Remove the application directory
exePath := GetExePath(pluginName)
if libs.PathExists(exePath) {
@ -413,6 +422,22 @@ func replacePlaceholdersInCmds(storeInfo *StoreInfo) {
},
)
}
if len(cmd.Cmds) > 0 {
for j, cmd := range cmd.Cmds {
strValue := convertValueToString(value)
// 使用正则表达式替换Content中的占位符
storeInfo.Commands[cmdGroupName][i].Cmds[j] = pattern.ReplaceAllStringFunc(
cmd,
func(match string) string {
// 检查占位符是否与Config中的键匹配
if strings.Trim(match, "{}") == key {
return strValue
}
return match
},
)
}
}
}
}

153
godo/store/os.go

@ -0,0 +1,153 @@
package store
import (
"bufio"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"syscall"
)
// DetectOperatingSystem returns the operating system name and version.
func DetectOperatingSystem() (name string, version string, err error) {
switch runtime.GOOS {
case "darwin":
// macOS specific detection
cmd := exec.Command("sw_vers")
out, err := cmd.CombinedOutput()
if err != nil {
return "", "", err
}
output := string(out)
lines := strings.Split(output, "\n")
if len(lines) >= 2 {
name = strings.TrimSpace(strings.Split(lines[0], ":")[1])
version = strings.TrimSpace(strings.Split(lines[1], ":")[1])
}
case "linux":
// Linux specific detection
file, err := os.Open("/etc/os-release")
if err != nil {
return "", "", err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "ID=") {
name = strings.TrimPrefix(line, "ID=")
name = strings.Trim(name, "\"")
} else if strings.HasPrefix(line, "VERSION_ID=") {
version = strings.TrimPrefix(line, "VERSION_ID=")
version = strings.Trim(version, "\"")
}
}
if err := scanner.Err(); err != nil {
return "", "", err
}
if name == "" || version == "" {
return "", "", fmt.Errorf("could not determine Linux distribution from /etc/os-release")
}
case "windows":
// Windows specific detection
cmd := exec.Command("cmd", "/c", "ver")
out, err := cmd.CombinedOutput()
if err != nil {
return "", "", err
}
version = strings.TrimSpace(string(out))
name = "Windows"
default:
// For other operating systems, just return the GOOS value
name = runtime.GOOS
version = ""
}
return name, version, nil
}
// DetectPackageManager returns the package manager based on the detected operating system.
func DetectPackageManager() (pm string, err error) {
switch runtime.GOOS {
case "darwin":
// macOS specific detection
cmd := exec.Command("which", "brew")
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("homebrew not found: %w", err)
}
pm = strings.TrimSpace(string(out))
if pm != "" {
pm = "brew"
}
case "linux":
// Linux specific detection
file, err := os.Open("/etc/os-release")
if err != nil {
return "", err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "ID=") {
id := strings.TrimPrefix(line, "ID=")
id = strings.Trim(id, "\"")
switch id {
case "debian", "ubuntu", "linuxmint", "elementary":
pm = "apt"
case "centos", "fedora", "rhel":
cmd := exec.Command("which", "dnf")
out, err := cmd.CombinedOutput()
if err != nil {
cmd = exec.Command("which", "yum")
out, err = cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("neither dnf nor yum found: %w", err)
}
}
pm = strings.TrimSpace(string(out))
if pm != "" {
pm = "dnf"
} else {
pm = "yum"
}
}
}
}
if err := scanner.Err(); err != nil {
return "", err
}
case "windows":
// Windows specific detection - Not applicable for this context
return "", fmt.Errorf("windows does not use apt, yum, or brew")
default:
// For other operating systems, just return an error
return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
}
return pm, nil
}
func CheckPathInstalled(execName string, execCommands string) bool {
cmd := exec.Command(execName, execCommands)
out, err := cmd.CombinedOutput()
if err != nil {
// 如果命令执行失败,可能是因为brew没有安装
if exitErr, ok := err.(*exec.ExitError); ok {
// 判断是否是因为找不到命令导致的错误
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 127 {
return false // brew没有安装
}
}
}
fmt.Printf("Homebrew version: %s\n", out)
return true
}

91
godo/store/port.go

@ -9,7 +9,6 @@ import (
"net"
"net/http"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
@ -26,7 +25,7 @@ type AllProcessesResponse struct {
Processes []ProcessSystemInfo `json:"processes"`
}
var processInfoRegex = regexp.MustCompile(`(\d+)\s+.*:\s*(\d+)\s+.*LISTEN\s+.*:(\d+)`)
//var processInfoRegex = regexp.MustCompile(`(\d+)\s+.*:\s*(\d+)\s+.*LISTEN\s+.*:(\d+)`)
func listAllProcesses() ([]ProcessSystemInfo, error) {
osType := runtime.GOOS
@ -37,7 +36,7 @@ func listAllProcesses() ([]ProcessSystemInfo, error) {
switch osType {
case "darwin", "linux":
cmd = exec.Command("lsof", "-i", "-n", "-P")
cmd = exec.Command("lsof", "-i")
case "windows":
cmd = exec.Command("netstat", "-ano")
cmd = SetHideConsoleCursor(cmd)
@ -50,31 +49,34 @@ func listAllProcesses() ([]ProcessSystemInfo, error) {
if err != nil {
return nil, fmt.Errorf("failed to list all processes: %v", err)
}
//lsofRegex := regexp.MustCompile(`(\d+)\s+(\S+\s+\S+)\s+(\d+)\s+(\w+)\s+IPv4\s+(\w+)\s+(\w+)\s+TCP\s+(\S+)\->\s+(\S+)\s+\((\w+)\)`)
processes := make([]ProcessSystemInfo, 0)
// 初始化映射用于去重
seenPIDs := make(map[int]bool)
// 解析输出
switch osType {
case "darwin", "linux":
scanner := bufio.NewScanner(bytes.NewBuffer(output)) // 使用bufio.Scanner
scanner := bufio.NewScanner(bytes.NewBuffer(output))
for scanner.Scan() {
line := scanner.Text()
matches := processInfoRegex.FindStringSubmatch(line)
if matches != nil {
pid, _ := strconv.Atoi(matches[1])
port, _ := strconv.Atoi(matches[3])
processName, err := getProcessName(osType, pid)
if err != nil {
log.Printf("Failed to get process name for PID %d: %v", pid, err)
continue
fields := strings.Fields(line)
if len(fields) >= 9 && fields[7] == "TCP" {
// 解析 PID 和其他字段
pid, _ := strconv.Atoi(fields[1])
name := fields[0]
// 解析本地端口
localPortStr := strings.Split(fields[8], ":")[1]
localPort, _ := strconv.Atoi(localPortStr)
if !seenPIDs[pid] {
seenPIDs[pid] = true
processes = append(processes, ProcessSystemInfo{
PID: pid,
Port: localPort,
Proto: "TCP",
Name: name,
})
}
processes = append(processes, ProcessSystemInfo{
PID: pid,
Port: port,
Proto: matches[2],
Name: processName,
})
}
}
case "windows":
@ -101,12 +103,15 @@ func listAllProcesses() ([]ProcessSystemInfo, error) {
log.Printf("Failed to convert port to integer: %v", err)
continue
}
processes = append(processes, ProcessSystemInfo{
PID: pid,
Port: portInt,
Proto: fields[0],
Name: processName,
})
if !seenPIDs[pid] {
seenPIDs[pid] = true
processes = append(processes, ProcessSystemInfo{
PID: pid,
Port: portInt,
Proto: fields[0],
Name: processName,
})
}
}
}
}
@ -156,19 +161,39 @@ func killProcessByName(name string) error {
switch osType {
case "darwin", "linux":
cmd = exec.Command("pkill", name)
// 使用 pgrep 查找进程 PID
pgrepCmd := exec.Command("pgrep", "-f", name)
pgrepOutput, err := pgrepCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to find process with name %s: %v", name, err)
}
// 将输出转换为字符串并按行分割
pids := strings.Split(strings.TrimSpace(string(pgrepOutput)), "\n")
// 对于每个找到的 PID,使用 kill 命令杀死进程
for _, pidStr := range pids {
pid, err := strconv.Atoi(pidStr)
if err != nil {
log.Printf("Failed to convert PID to integer: %v", err)
continue
}
// 每次循环使用新的 *exec.Cmd 实例
killCmd := exec.Command("kill", "-9", strconv.Itoa(pid))
if err := killCmd.Run(); err != nil {
log.Printf("Failed to kill process with PID %d: %v", pid, err)
}
}
case "windows":
cmd = exec.Command("taskkill", "/IM", name, "/F") // /F 表示强制结束
cmd = SetHideConsoleCursor(cmd)
err = cmd.Run()
if err != nil {
log.Printf("Failed to kill process with name %s: %v", name, err)
}
default:
return fmt.Errorf("unsupported operating system")
}
err = cmd.Run()
if err != nil {
log.Printf("Failed to kill process with name %s: %v", name, err)
}
return err
}

8
godo/store/store.go

@ -150,10 +150,10 @@ func GetStoreInfoByPath(infoPath string) (StoreInfo, error) {
if err := json.Unmarshal(content, &storeInfo); err != nil {
return storeInfo, fmt.Errorf("failed to unmarshal info.json: %v", err)
}
scriptPath := storeInfo.Setting.BinPath
if !libs.PathExists(scriptPath) {
return storeInfo, fmt.Errorf("script file '%s' not found", scriptPath)
}
// scriptPath := storeInfo.Setting.BinPath
// if !libs.PathExists(scriptPath) {
// return storeInfo, fmt.Errorf("script file '%s' not found", scriptPath)
// }
return storeInfo, nil
}
func GetInstalled() []InstallInfo {

Loading…
Cancel
Save