From 0fcdbdd3ac7cb54ddf98dd39ec860ff05c4921c8 Mon Sep 17 00:00:00 2001 From: skyboy Date: Fri, 2 Aug 2024 11:51:25 +0800 Subject: [PATCH] add macos appstore --- build.linux.sh | 29 +- docs/Store.md | 4 +- frontend/package-lock.json | 13 +- godo/deps/darwin/brew-install.sh | 1100 ++++++++++++++++++++++++++++++ godo/go.mod | 2 +- godo/go.sum | 2 - godo/store/cmds.go | 64 +- godo/store/install.go | 25 + godo/store/os.go | 153 +++++ 9 files changed, 1353 insertions(+), 39 deletions(-) mode change 100644 => 100755 build.linux.sh create mode 100755 godo/deps/darwin/brew-install.sh create mode 100644 godo/store/os.go diff --git a/build.linux.sh b/build.linux.sh old mode 100644 new mode 100755 index a864dae..1b64392 --- a/build.linux.sh +++ b/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 \ No newline at end of file +#!/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" \ No newline at end of file diff --git a/docs/Store.md b/docs/Store.md index 0b128b0..fad114e 100644 --- a/docs/Store.md +++ b/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目录,尝试自己制作安装包 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6b9b1b9..4844e0d 100644 --- a/frontend/package-lock.json +++ b/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" diff --git a/godo/deps/darwin/brew-install.sh b/godo/deps/darwin/brew-install.sh new file mode 100755 index 0000000..639672f --- /dev/null +++ b/godo/deps/darwin/brew-install.sh @@ -0,0 +1,1100 @@ +#!/bin/bash + +# We don't need return codes for "$(command)", only stdout is needed. +# Allow `[[ -n "$(command)" ]]`, `func "$(command)"`, pipes, etc. +# shellcheck disable=SC2312 + +set -u + +abort() { + printf "%s\n" "$@" >&2 + exit 1 +} + +# Fail fast with a concise message when not using bash +# Single brackets are needed here for POSIX compatibility +# shellcheck disable=SC2292 +if [ -z "${BASH_VERSION:-}" ] +then + abort "Bash is required to interpret this script." +fi + +# Check if script is run with force-interactive mode in CI +if [[ -n "${CI-}" && -n "${INTERACTIVE-}" ]] +then + abort "Cannot run force-interactive mode in CI." +fi + +# Check if both `INTERACTIVE` and `NONINTERACTIVE` are set +# Always use single-quoted strings with `exp` expressions +# shellcheck disable=SC2016 +if [[ -n "${INTERACTIVE-}" && -n "${NONINTERACTIVE-}" ]] +then + abort 'Both `$INTERACTIVE` and `$NONINTERACTIVE` are set. Please unset at least one variable and try again.' +fi + +# Check if script is run in POSIX mode +if [[ -n "${POSIXLY_CORRECT+1}" ]] +then + abort 'Bash must not run in POSIX mode. Please unset POSIXLY_CORRECT and try again.' +fi + +usage() { + cat <${tty_bold} %s${tty_reset}\n" "$(shell_join "$@")" +} + +warn() { + printf "${tty_red}Warning${tty_reset}: %s\n" "$(chomp "$1")" >&2 +} + +# Check if script is run non-interactively (e.g. CI) +# If it is run non-interactively we should not prompt for passwords. +# Always use single-quoted strings with `exp` expressions +# shellcheck disable=SC2016 +if [[ -z "${NONINTERACTIVE-}" ]] +then + if [[ -n "${CI-}" ]] + then + warn 'Running in non-interactive mode because `$CI` is set.' + NONINTERACTIVE=1 + elif [[ ! -t 0 ]] + then + if [[ -z "${INTERACTIVE-}" ]] + then + warn 'Running in non-interactive mode because `stdin` is not a TTY.' + NONINTERACTIVE=1 + else + warn 'Running in interactive mode despite `stdin` not being a TTY because `$INTERACTIVE` is set.' + fi + fi +else + ohai 'Running in non-interactive mode because `$NONINTERACTIVE` is set.' +fi + +# USER isn't always set so provide a fall back for the installer and subprocesses. +if [[ -z "${USER-}" ]] +then + USER="$(chomp "$(id -un)")" + export USER +fi + +# First check OS. +OS="$(uname)" +if [[ "${OS}" == "Linux" ]] +then + HOMEBREW_ON_LINUX=1 +elif [[ "${OS}" == "Darwin" ]] +then + HOMEBREW_ON_MACOS=1 +else + abort "Homebrew is only supported on macOS and Linux." +fi + +# Required installation paths. To install elsewhere (which is unsupported) +# you can untar https://github.com/Homebrew/brew/tarball/master +# anywhere you like. +if [[ -n "${HOMEBREW_ON_MACOS-}" ]] +then + UNAME_MACHINE="$(/usr/bin/uname -m)" + + if [[ "${UNAME_MACHINE}" == "arm64" ]] + then + # On ARM macOS, this script installs to /opt/homebrew only + HOMEBREW_PREFIX="/opt/homebrew" + HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}" + else + # On Intel macOS, this script installs to /usr/local only + HOMEBREW_PREFIX="/usr/local" + HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}/Homebrew" + fi + HOMEBREW_CACHE="${HOME}/Library/Caches/Homebrew" + + STAT_PRINTF=("/usr/bin/stat" "-f") + PERMISSION_FORMAT="%A" + CHOWN=("/usr/sbin/chown") + CHGRP=("/usr/bin/chgrp") + GROUP="admin" + TOUCH=("/usr/bin/touch") + INSTALL=("/usr/bin/install" -d -o "root" -g "wheel" -m "0755") +else + UNAME_MACHINE="$(uname -m)" + + # On Linux, this script installs to /home/linuxbrew/.linuxbrew only + HOMEBREW_PREFIX="/home/linuxbrew/.linuxbrew" + HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}/Homebrew" + HOMEBREW_CACHE="${HOME}/.cache/Homebrew" + + STAT_PRINTF=("/usr/bin/stat" "--printf") + PERMISSION_FORMAT="%a" + CHOWN=("/bin/chown") + CHGRP=("/bin/chgrp") + GROUP="$(id -gn)" + TOUCH=("/bin/touch") + INSTALL=("/usr/bin/install" -d -o "${USER}" -g "${GROUP}" -m "0755") +fi +CHMOD=("/bin/chmod") +MKDIR=("/bin/mkdir" "-p") +HOMEBREW_BREW_DEFAULT_GIT_REMOTE="https://github.com/Homebrew/brew" +HOMEBREW_CORE_DEFAULT_GIT_REMOTE="https://github.com/Homebrew/homebrew-core" + +# Use remote URLs of Homebrew repositories from environment if set. +HOMEBREW_BREW_GIT_REMOTE="${HOMEBREW_BREW_GIT_REMOTE:-"${HOMEBREW_BREW_DEFAULT_GIT_REMOTE}"}" +HOMEBREW_CORE_GIT_REMOTE="${HOMEBREW_CORE_GIT_REMOTE:-"${HOMEBREW_CORE_DEFAULT_GIT_REMOTE}"}" +# The URLs with and without the '.git' suffix are the same Git remote. Do not prompt. +if [[ "${HOMEBREW_BREW_GIT_REMOTE}" == "${HOMEBREW_BREW_DEFAULT_GIT_REMOTE}.git" ]] +then + HOMEBREW_BREW_GIT_REMOTE="${HOMEBREW_BREW_DEFAULT_GIT_REMOTE}" +fi +if [[ "${HOMEBREW_CORE_GIT_REMOTE}" == "${HOMEBREW_CORE_DEFAULT_GIT_REMOTE}.git" ]] +then + HOMEBREW_CORE_GIT_REMOTE="${HOMEBREW_CORE_DEFAULT_GIT_REMOTE}" +fi +export HOMEBREW_{BREW,CORE}_GIT_REMOTE + +# TODO: bump version when new macOS is released or announced +MACOS_NEWEST_UNSUPPORTED="15.0" +# TODO: bump version when new macOS is released +MACOS_OLDEST_SUPPORTED="12.0" + +# For Homebrew on Linux +REQUIRED_RUBY_VERSION=2.6 # https://github.com/Homebrew/brew/pull/6556 +REQUIRED_GLIBC_VERSION=2.13 # https://docs.brew.sh/Homebrew-on-Linux#requirements +REQUIRED_CURL_VERSION=7.41.0 # HOMEBREW_MINIMUM_CURL_VERSION in brew.sh in Homebrew/brew +REQUIRED_GIT_VERSION=2.7.0 # HOMEBREW_MINIMUM_GIT_VERSION in brew.sh in Homebrew/brew + +# no analytics during installation +export HOMEBREW_NO_ANALYTICS_THIS_RUN=1 +export HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT=1 + +unset HAVE_SUDO_ACCESS # unset this from the environment + +have_sudo_access() { + if [[ ! -x "/usr/bin/sudo" ]] + then + return 1 + fi + + local -a SUDO=("/usr/bin/sudo") + if [[ -n "${SUDO_ASKPASS-}" ]] + then + SUDO+=("-A") + elif [[ -n "${NONINTERACTIVE-}" ]] + then + SUDO+=("-n") + fi + + if [[ -z "${HAVE_SUDO_ACCESS-}" ]] + then + if [[ -n "${NONINTERACTIVE-}" ]] + then + "${SUDO[@]}" -l mkdir &>/dev/null + else + "${SUDO[@]}" -v && "${SUDO[@]}" -l mkdir &>/dev/null + fi + HAVE_SUDO_ACCESS="$?" + fi + + if [[ -n "${HOMEBREW_ON_MACOS-}" ]] && [[ "${HAVE_SUDO_ACCESS}" -ne 0 ]] + then + abort "Need sudo access on macOS (e.g. the user ${USER} needs to be an Administrator)!" + fi + + return "${HAVE_SUDO_ACCESS}" +} + +execute() { + if ! "$@" + then + abort "$(printf "Failed during: %s" "$(shell_join "$@")")" + fi +} + +execute_sudo() { + local -a args=("$@") + if [[ "${EUID:-${UID}}" != "0" ]] && have_sudo_access + then + if [[ -n "${SUDO_ASKPASS-}" ]] + then + args=("-A" "${args[@]}") + fi + ohai "/usr/bin/sudo" "${args[@]}" + execute "/usr/bin/sudo" "${args[@]}" + else + ohai "${args[@]}" + execute "${args[@]}" + fi +} + +getc() { + local save_state + save_state="$(/bin/stty -g)" + /bin/stty raw -echo + IFS='' read -r -n 1 -d '' "$@" + /bin/stty "${save_state}" +} + +ring_bell() { + # Use the shell's audible bell. + if [[ -t 1 ]] + then + printf "\a" + fi +} + +wait_for_user() { + local c + echo + echo "Press ${tty_bold}RETURN${tty_reset}/${tty_bold}ENTER${tty_reset} to continue or any other key to abort:" + getc c + # we test for \r and \n because some stuff does \r instead + if ! [[ "${c}" == $'\r' || "${c}" == $'\n' ]] + then + exit 1 + fi +} + +major_minor() { + echo "${1%%.*}.$( + x="${1#*.}" + echo "${x%%.*}" + )" +} + +version_gt() { + [[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -gt "${2#*.}" ]] +} +version_ge() { + [[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -ge "${2#*.}" ]] +} +version_lt() { + [[ "${1%.*}" -lt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -lt "${2#*.}" ]] +} + +check_run_command_as_root() { + [[ "${EUID:-${UID}}" == "0" ]] || return + + # Allow Azure Pipelines/GitHub Actions/Docker/Concourse/Kubernetes to do everything as root (as it's normal there) + [[ -f /.dockerenv ]] && return + [[ -f /run/.containerenv ]] && return + [[ -f /proc/1/cgroup ]] && grep -E "azpl_job|actions_job|docker|garden|kubepods" -q /proc/1/cgroup && return + + abort "Don't run this as root!" +} + +should_install_command_line_tools() { + if [[ -n "${HOMEBREW_ON_LINUX-}" ]] + then + return 1 + fi + + if version_gt "${macos_version}" "10.13" + then + ! [[ -e "/Library/Developer/CommandLineTools/usr/bin/git" ]] + else + ! [[ -e "/Library/Developer/CommandLineTools/usr/bin/git" ]] || + ! [[ -e "/usr/include/iconv.h" ]] + fi +} + +get_permission() { + "${STAT_PRINTF[@]}" "${PERMISSION_FORMAT}" "$1" +} + +user_only_chmod() { + [[ -d "$1" ]] && [[ "$(get_permission "$1")" != 75[0145] ]] +} + +exists_but_not_writable() { + [[ -e "$1" ]] && ! [[ -r "$1" && -w "$1" && -x "$1" ]] +} + +get_owner() { + "${STAT_PRINTF[@]}" "%u" "$1" +} + +file_not_owned() { + [[ "$(get_owner "$1")" != "$(id -u)" ]] +} + +get_group() { + "${STAT_PRINTF[@]}" "%g" "$1" +} + +file_not_grpowned() { + [[ " $(id -G "${USER}") " != *" $(get_group "$1") "* ]] +} + +# Please sync with 'test_ruby()' in 'Library/Homebrew/utils/ruby.sh' from the Homebrew/brew repository. +test_ruby() { + if [[ ! -x "$1" ]] + then + return 1 + fi + + "$1" --enable-frozen-string-literal --disable=gems,did_you_mean,rubyopt -rrubygems -e \ + "abort if Gem::Version.new(RUBY_VERSION.to_s.dup).to_s.split('.').first(2) != \ + Gem::Version.new('${REQUIRED_RUBY_VERSION}').to_s.split('.').first(2)" 2>/dev/null +} + +test_curl() { + if [[ ! -x "$1" ]] + then + return 1 + fi + + local curl_version_output curl_name_and_version + curl_version_output="$("$1" --version 2>/dev/null)" + curl_name_and_version="${curl_version_output%% (*}" + version_ge "$(major_minor "${curl_name_and_version##* }")" "$(major_minor "${REQUIRED_CURL_VERSION}")" +} + +test_git() { + if [[ ! -x "$1" ]] + then + return 1 + fi + + local git_version_output + git_version_output="$("$1" --version 2>/dev/null)" + if [[ "${git_version_output}" =~ "git version "([^ ]*).* ]] + then + version_ge "$(major_minor "${BASH_REMATCH[1]}")" "$(major_minor "${REQUIRED_GIT_VERSION}")" + else + abort "Unexpected Git version: '${git_version_output}'!" + fi +} + +# Search for the given executable in PATH (avoids a dependency on the `which` command) +which() { + # Alias to Bash built-in command `type -P` + type -P "$@" +} + +# Search PATH for the specified program that satisfies Homebrew requirements +# function which is set above +# shellcheck disable=SC2230 +find_tool() { + if [[ $# -ne 1 ]] + then + return 1 + fi + + local executable + while read -r executable + do + if [[ "${executable}" != /* ]] + then + warn "Ignoring ${executable} (relative paths don't work)" + elif "test_$1" "${executable}" + then + echo "${executable}" + break + fi + done < <(which -a "$1") +} + +no_usable_ruby() { + [[ -z "$(find_tool ruby)" ]] +} + +outdated_glibc() { + local glibc_version + glibc_version="$(ldd --version | head -n1 | grep -o '[0-9.]*$' | grep -o '^[0-9]\+\.[0-9]\+')" + version_lt "${glibc_version}" "${REQUIRED_GLIBC_VERSION}" +} + +if [[ -n "${HOMEBREW_ON_LINUX-}" ]] && no_usable_ruby && outdated_glibc +then + abort "$( + cat </dev/null +then + trap '/usr/bin/sudo -k' EXIT +fi + +# Things can fail later if `pwd` doesn't exist. +# Also sudo prints a warning message for no good reason +cd "/usr" || exit 1 + +####################################################################### script + +# shellcheck disable=SC2016 +ohai 'Checking for `sudo` access (which may request your password)...' + +if [[ -n "${HOMEBREW_ON_MACOS-}" ]] +then + [[ "${EUID:-${UID}}" == "0" ]] || have_sudo_access +elif ! [[ -w "${HOMEBREW_PREFIX}" ]] && + ! [[ -w "/home/linuxbrew" ]] && + ! [[ -w "/home" ]] && + ! have_sudo_access +then + abort "$( + cat </dev/null +then + abort "$( + cat </dev/null || return + + # we do it in four steps to avoid merge errors when reinstalling + execute "${USABLE_GIT}" "-c" "init.defaultBranch=master" "init" "--quiet" + + # "git remote add" will fail if the remote is defined in the global config + execute "${USABLE_GIT}" "config" "remote.origin.url" "${HOMEBREW_BREW_GIT_REMOTE}" + execute "${USABLE_GIT}" "config" "remote.origin.fetch" "+refs/heads/*:refs/remotes/origin/*" + + # ensure we don't munge line endings on checkout + execute "${USABLE_GIT}" "config" "--bool" "core.autocrlf" "false" + + # make sure symlinks are saved as-is + execute "${USABLE_GIT}" "config" "--bool" "core.symlinks" "true" + + execute "${USABLE_GIT}" "fetch" "--force" "origin" + execute "${USABLE_GIT}" "fetch" "--force" "--tags" "origin" + execute "${USABLE_GIT}" "remote" "set-head" "origin" "--auto" >/dev/null + + LATEST_GIT_TAG="$("${USABLE_GIT}" tag --list --sort="-version:refname" | head -n1)" + if [[ -z "${LATEST_GIT_TAG}" ]] + then + abort "Failed to query latest Homebrew/brew Git tag." + fi + execute "${USABLE_GIT}" "checkout" "--force" "-B" "stable" "${LATEST_GIT_TAG}" + + if [[ "${HOMEBREW_REPOSITORY}" != "${HOMEBREW_PREFIX}" ]] + then + if [[ "${HOMEBREW_REPOSITORY}" == "${HOMEBREW_PREFIX}/Homebrew" ]] + then + execute "ln" "-sf" "../Homebrew/bin/brew" "${HOMEBREW_PREFIX}/bin/brew" + else + abort "The Homebrew/brew repository should be placed in the Homebrew prefix directory." + fi + fi + + if [[ -n "${HOMEBREW_NO_INSTALL_FROM_API-}" && ! -d "${HOMEBREW_CORE}" ]] + then + # Always use single-quoted strings with `exp` expressions + # shellcheck disable=SC2016 + ohai 'Tapping homebrew/core because `$HOMEBREW_NO_INSTALL_FROM_API` is set.' + ( + execute "${MKDIR[@]}" "${HOMEBREW_CORE}" + cd "${HOMEBREW_CORE}" >/dev/null || return + + execute "${USABLE_GIT}" "-c" "init.defaultBranch=master" "init" "--quiet" + execute "${USABLE_GIT}" "config" "remote.origin.url" "${HOMEBREW_CORE_GIT_REMOTE}" + execute "${USABLE_GIT}" "config" "remote.origin.fetch" "+refs/heads/*:refs/remotes/origin/*" + execute "${USABLE_GIT}" "config" "--bool" "core.autocrlf" "false" + execute "${USABLE_GIT}" "config" "--bool" "core.symlinks" "true" + execute "${USABLE_GIT}" "fetch" "--force" "origin" "refs/heads/master:refs/remotes/origin/master" + execute "${USABLE_GIT}" "remote" "set-head" "origin" "--auto" >/dev/null + execute "${USABLE_GIT}" "reset" "--hard" "origin/master" + + cd "${HOMEBREW_REPOSITORY}" >/dev/null || return + ) || exit 1 + fi + + execute "${HOMEBREW_PREFIX}/bin/brew" "update" "--force" "--quiet" +) || exit 1 + +if [[ ":${PATH}:" != *":${HOMEBREW_PREFIX}/bin:"* ]] +then + warn "${HOMEBREW_PREFIX}/bin is not in your PATH. + Instructions on how to configure your shell for Homebrew + can be found in the 'Next steps' section below." +fi + +ohai "Installation successful!" +echo + +ring_bell + +# Use an extra newline and bold to avoid this being missed. +ohai "Homebrew has enabled anonymous aggregate formulae and cask analytics." +echo "$( + cat </dev/null || return + execute "${USABLE_GIT}" "config" "--replace-all" "homebrew.analyticsmessage" "true" + execute "${USABLE_GIT}" "config" "--replace-all" "homebrew.caskanalyticsmessage" "true" +) || exit 1 + +ohai "Next steps:" +case "${SHELL}" in + */bash*) + if [[ -n "${HOMEBREW_ON_LINUX-}" ]] + then + shell_rcfile="${HOME}/.bashrc" + else + shell_rcfile="${HOME}/.bash_profile" + fi + ;; + */zsh*) + if [[ -n "${HOMEBREW_ON_LINUX-}" ]] + then + shell_rcfile="${ZDOTDIR:-"${HOME}"}/.zshrc" + else + shell_rcfile="${ZDOTDIR:-"${HOME}"}/.zprofile" + fi + ;; + */fish*) + shell_rcfile="${HOME}/.config/fish/config.fish" + ;; + *) + shell_rcfile="${ENV:-"${HOME}/.profile"}" + ;; +esac + +if grep -qs "eval \"\$(${HOMEBREW_PREFIX}/bin/brew shellenv)\"" "${shell_rcfile}" +then + if ! [[ -x "$(command -v brew)" ]] + then + cat <> ${shell_rcfile} + eval "\$(${HOMEBREW_PREFIX}/bin/brew shellenv)" +EOS +fi + +if [[ -n "${non_default_repos}" ]] +then + plural="" + if [[ "${#additional_shellenv_commands[@]}" -gt 1 ]] + then + plural="s" + fi + printf -- "- Run these commands in your terminal to add the non-default Git remote%s for %s:\n" "${plural}" "${non_default_repos}" + printf " echo '# Set PATH, MANPATH, etc., for Homebrew.' >> %s\n" "${shell_rcfile}" + printf " echo '%s' >> ${shell_rcfile}\n" "${additional_shellenv_commands[@]}" + printf " %s\n" "${additional_shellenv_commands[@]}" +fi + +if [[ -n "${HOMEBREW_ON_LINUX-}" ]] +then + echo "- Install Homebrew's dependencies if you have sudo access:" + + if [[ -x "$(command -v apt-get)" ]] + then + echo " sudo apt-get install build-essential" + elif [[ -x "$(command -v yum)" ]] + then + echo " sudo yum groupinstall 'Development Tools'" + elif [[ -x "$(command -v pacman)" ]] + then + echo " sudo pacman -S base-devel" + elif [[ -x "$(command -v apk)" ]] + then + echo " sudo apk add build-base" + fi + + cat < 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) @@ -69,9 +59,46 @@ func runStart(storeInfo StoreInfo) error { } return nil } +func GetRunCmd(binPath string, cmds []string) (*exec.Cmd, error) { + var cmd *exec.Cmd + 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") + } + 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 +114,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) } diff --git a/godo/store/install.go b/godo/store/install.go index 15f2977..13dd3b4 100644 --- a/godo/store/install.go +++ b/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 + }, + ) + } + } } } diff --git a/godo/store/os.go b/godo/store/os.go new file mode 100644 index 0000000..26a871a --- /dev/null +++ b/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 +}