Update to v8.7.0

This commit is contained in:
aapanel.com
2026-04-16 18:17:07 +08:00
parent c3f2d7566c
commit ee92fa2dbd
2472 changed files with 33631 additions and 20919 deletions
+807
View File
@@ -0,0 +1,807 @@
#!/www/server/panel/pyenv/bin/python
import os
import re
import shutil
import sys
import time
import requests
import traceback
import tarfile
import subprocess
import argparse
from concurrent.futures import ThreadPoolExecutor
from typing import Optional, Tuple, List, Union, Dict, Iterable
from hashlib import sha256, sha1
from platform import system, machine
from xml.etree import cElementTree
from io import FileIO
__all__ = ['pygvm']
class _GvmSTD:
out = sys.stdout
err = sys.stderr
_gvm_std = _GvmSTD()
def get_arch() -> Tuple[Optional[str], Optional[str]]:
_system_name = system()
if _system_name != "Linux":
return None, "Currently, this feature is only supported for Linux systems."
_arch = machine().lower()
arch = None
if _arch in ("x86_64", "amd64"):
arch = "amd64"
elif _arch in ("i386", "i486", "i586", "x86"):
arch = "386"
elif _arch in ("aarch64", "arm64"):
arch = "arm64"
if arch is None:
return None, "Failed to parse the architecture of the current processor."
return arch, None
class GVMError(Exception):
pass
template = """#pygvm
export GOROOT="/usr/local/btgo"
export PATH="/usr/local/btgo/bin:${PATH}"
"""
class _Mirror(object):
"""
Mirror source
"""
_cloud = ""
name = ""
def test_speed(self, time_out: int) -> float:
try:
s_time = time.time()
res = requests.get(self._cloud, timeout=time_out)
if res.status_code != 200:
return sys.float_info.max
return time.time() - s_time
except:
return sys.float_info.max
def get_versions_by_cloud(self, arch: str) -> Tuple[Union[List["GoVersion"], str], Optional[List["GoVersion"]]]:
return "Failed to request cloud data! Please check your network connection.", None
def download_url(self, go_v: "GoVersion") -> str:
raise NotImplemented()
class _StudyGolangMirror(_Mirror):
_cloud = "https://studygolang.com/dl"
name = "Community Source"
def get_versions_by_cloud(self, arch: str) -> Tuple[Union[List["GoVersion"], str], Optional[List["GoVersion"]]]:
"""
Get archived and stable versions supported by the cloud
"""
res = requests.get(self._cloud)
if res.status_code != 200:
return "Failed to request cloud data! Please check your network connection.", None
rep = r'(<\s?h3\s?id="stable"\s?>.*\n)(?P<stable>(.*\n)+)(\s*<\s?h3\s?id="unstable"\s?>)(.*\n)+' \
r'(?P<history>(\s*<div\s?class="toggle(Visible)?"\s?id="archive"\s?>)(.*\n)+)((\s*</div>\s*\n){5}\s*<div)'
res = re.search(rep, res.text)
if not res:
return "Parsing error", None
try:
stable = cElementTree.fromstring("<body>" + res.group("stable") + "</body>")
history = cElementTree.fromstring(res.group("history")).find('./div[2]')
stable_go_versions = self._parser_xml(stable, arch)
history_go_versions = self._parser_xml(history, arch)
stable_go_versions.sort(key=lambda x: int(x.version.split(".")[1]), reverse=True)
history_go_versions.sort(key=lambda x: int(x.version.split(".")[1]), reverse=True)
return stable_go_versions, history_go_versions
except:
traceback.print_exc(file=_gvm_std.err)
return "Parsing error", None
@staticmethod
def _parser_xml(datas: cElementTree.Element, arch: str) -> List["GoVersion"]:
res_list = []
for data in datas.findall('./div'):
v = data.attrib.get("id")
need_file = "{}.linux-{}.tar.gz".format(v, arch)
for i in data.iterfind('.//tr'):
if i.attrib.get("class") == "first":
continue
file_name = i.find("./td[1]/a").text
checksum = i.find("./td[6]/tt").text
if need_file == file_name:
go_v = GoVersion.new(v, checksum, file_name)
if go_v:
res_list.append(go_v)
return res_list
def download_url(self, go_v: "GoVersion") -> str:
return "{}/golang/{}".format(self._cloud, go_v.file_name)
class _GoOfficialDeVMirror(_Mirror):
_cloud = "https://go.dev/dl/"
name = "go.dev source"
def get_versions_by_cloud(self, arch: str) -> Tuple[Union[List["GoVersion"], str], Optional[List["GoVersion"]]]:
"""
Get archived and stable versions supported by the cloud
"""
res = requests.get(self._cloud)
if res.status_code != 200:
return "Failed to request cloud data! Please check your network connection.", None
rep_stable_versions = re.compile(r'<div\s+class="toggleVisible"\s+id="(.|\n)*?</table>(\s+</div>){3}')
rep_history_versions = re.compile(r'<div\s+class="toggle"\s+id="(.|\n)*?</table>(\s+</div>){3}')
stable_versions, history_versions = [], []
for i in rep_stable_versions.finditer(res.text):
g = self._parser_xml_node(i.group(), arch)
if g is not None:
stable_versions.append(g)
for i in rep_history_versions.finditer(res.text):
g = self._parser_xml_node(i.group(), arch)
if g is not None:
history_versions.append(g)
if not stable_versions and history_versions:
return "Parsing error", None
return stable_versions, history_versions
@staticmethod
def _parser_xml_node(data: str, arch: str) -> Optional["GoVersion"]:
try:
node = cElementTree.fromstring(data)
v = node.attrib.get("id")
if v.find("rc") != -1 or v.find("beta") != -1:
return None
need_file = "{}.linux-{}.tar.gz".format(v, arch)
for i in node.iterfind('.//tr'):
if i.attrib.get("class") == "first":
continue
file_name = i.find("./td[1]/a").text
checksum = i.find("./td[6]/tt").text
if need_file == file_name:
go_v = GoVersion.new(v, checksum, file_name)
if go_v:
return go_v
except:
return None
return None
def download_url(self, go_v: "GoVersion") -> str:
return "{}/{}".format(self._cloud, go_v.file_name)
class _GoOfficialCNMirror(_GoOfficialDeVMirror):
_cloud = "https://golang.google.cn/dl/"
name = "official"
# 2026-01-16 Verified that the USTC mirror cannot be used
class _UstcMirror(_Mirror):
_cloud = "https://mirrors.ustc.edu.cn/golang/"
name = "ustc"
def get_versions_by_cloud(self, arch: str) -> Tuple[Union[List["GoVersion"], str], Optional[List["GoVersion"]]]:
"""
Get archived and stable versions supported by the cloud
"""
res = requests.get(self._cloud)
if res.status_code != 200:
return "Failed to request cloud data! Please check your network connection.", None
rep_version = re.compile(r"<a.*(?P<f>(?P<v>go[\d.]+)\.linux-%s\.tar\.gz(\.sha256)?)" % arch)
v_map = {}
for i in rep_version.finditer(res.text):
v = i.group("v")
if v not in v_map:
v_map[v] = [v, "", ""]
f = i.group("f")
if f.endswith(".sha256"):
v_map[v][1] = f
else:
v_map[v][2] = f
try:
v_list = self._get_sha256(list(v_map.values()))
except:
v_list = []
if not v_list:
return "Parsing error", None
return [], v_list
def _get_sha256(self, v_list: List[Iterable[str]]) -> List["GoVersion"]:
p = ThreadPoolExecutor(10)
t_list = []
for idx, (v, s, f) in enumerate(v_list):
if not s:
continue
t = p.submit(self._query_sha256, v_list, s, idx)
t_list.append(t)
for i in t_list:
i.result()
p.shutdown()
use_list = []
for v, s, f in v_list:
g = GoVersion.new(v, s, f)
if g:
use_list.append(g)
return use_list
def _query_sha256(self, v_list, s, idx):
try:
res = requests.get("{}/{}".format(self._cloud, s))
if res.status_code == 200:
v_list[idx][1] = res.text
except:
v_list[idx][1] = ""
def download_url(self, go_v: "GoVersion") -> str:
return "{}/{}".format(self._cloud, go_v.file_name)
def get_cloud() -> _Mirror:
_m_list = [
_StudyGolangMirror(),
_GoOfficialCNMirror(),
_GoOfficialDeVMirror(), # Unstable disable
]
v4_file = '/www/server/panel/data/v4.pl'
if os.path.exists(v4_file):
try:
with open(v4_file, 'rt', encoding='utf-8') as fp:
if fp.read().strip() == "-4":
_m_list = _m_list[1:]
except:
pass
def test_with_time_out(time_out: int) -> _Mirror:
p = ThreadPoolExecutor(len(_m_list))
res_task = []
for m in _m_list:
res_task.append(p.submit(m.test_speed, time_out))
time_list = []
for idx, t in enumerate(res_task):
time_list.append((t.result(timeout=None), idx))
time_list.sort(key=lambda x: x[0])
return _m_list[time_list[0][1]]
try:
return test_with_time_out(10)
except:
try:
return test_with_time_out(60)
except:
pass
return _Mirror()
class GoVersion:
__slots__ = ("version", "checksum", "file_name", "_cloud", "bt_gvm_path")
def __init__(self, v: str, checksum: str, file_name: str):
self.version = v
self.checksum = checksum
self.file_name = file_name
self._cloud: Optional[_Mirror] = None
self.bt_gvm_path = "/usr/local/btgojdk"
def set_cloud(self, c: _Mirror):
self._cloud = c
print("Using [{}] source for download".format(c.name))
def check(self, file) -> bool:
"""calculate file sha256 """
print("[2/4] Verifying file hash.....", file=_gvm_std.out, flush=True)
sha_obj = sha256() if len(self.checksum) == 64 else sha1()
with open(file, 'rb') as f:
for chunk in iter(lambda: f.read(4096), b''):
sha_obj.update(chunk)
hash_value = sha_obj.hexdigest()
print("Calculated value: {}".format(hash_value), file=_gvm_std.out, flush=True)
print("Expected value: {}".format(self.checksum), file=_gvm_std.out, flush=True)
return hash_value == self.checksum
@classmethod
def new(cls, v: str, checksum: str, file_name: str) -> Optional["GoVersion"]:
v_rep = r"(?P<target>go1\.\d{1,2}(\.\d{1,2})?)"
v_res = re.search(v_rep, v)
v = None
if v_res:
v = v_res.group("target")
checksum_rep = r"(?P<target>([0-9a-f]{64})|([0-9a-f]{40}))"
checksum_res = re.search(checksum_rep, checksum)
checksum = None
if checksum_res:
checksum = checksum_res.group("target")
if not v or not checksum_res:
return None
return cls(v, checksum, file_name)
def __str__(self) -> str:
return "[v:{}, checksum:{}]".format(self.version, self.checksum)
def serialize(self) -> str:
return "({}:{}:{})".format(self.version, self.checksum, self.file_name)
def show(self) -> str:
return self.version
@classmethod
def deserialize(cls, data: str) -> Optional["GoVersion"]:
if data[0] == "(" and data[-1] == ")":
data = data[1:-1]
return cls.new(*data.split(":")[:3])
def _download(self) -> bool:
cache_dir = os.path.join(self.bt_gvm_path, "cached")
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
dst = os.path.join(cache_dir, self.file_name)
if os.path.exists(dst):
print("[1/4] Using cached SDK file......", file=_gvm_std.out, flush=True)
return self.check(dst)
print("[1/4] Downloading SDK file......", file=_gvm_std.out, flush=True)
response = requests.get(self._cloud.download_url(self), stream=True)
total_size = int(response.headers.get('content-length', 0))
print("File size to download: %.2fM" % (total_size / (1024 * 1024)), file=_gvm_std.out, flush=True)
block_size = 1024 * 1024
downloaded_size = 0
with open(dst, 'wb') as f:
for data in response.iter_content(block_size):
f.write(data)
downloaded_size += len(data)
progress = (downloaded_size / total_size) * 100
print(f"Downloading....\t %.2f%% completed" % progress,
end='\r', flush=True, file=_gvm_std.out)
print('Download complete', file=_gvm_std.out, flush=True)
return self.check(dst)
def _install(self) -> bool:
print("[3/4] Extracting and installing.....", file=_gvm_std.out, flush=True)
tar_filename = os.path.join(self.bt_gvm_path, "cached", self.file_name)
target_directory = self.bt_gvm_path
try:
with tarfile.open(tar_filename, 'r:gz') as tar:
tar.extractall(path=target_directory)
except:
return False
src = os.path.join(self.bt_gvm_path, "go")
if not os.path.isdir(src):
return False
os.rename(src, os.path.join(self.bt_gvm_path, self.version))
return True
def install(self) -> Tuple[bool, str]:
if not os.path.exists(self.bt_gvm_path):
os.makedirs(self.bt_gvm_path)
print("Starting installation......", file=_gvm_std.out, flush=True)
dst = os.path.join(self.bt_gvm_path, self.version)
if os.path.isdir(dst):
return True, "Already installed"
# Download file
if not self._download():
return False, "File download and verification failed!"
# Install go
if not self._install():
return False, "Extraction and installation failed!"
print("[4/4] Using {}.....".format(self.version), file=_gvm_std.out, flush=True)
flag, err = self.use(self.bt_gvm_path, self.version)
if not flag:
return False, err
print("Installation complete!", file=_gvm_std.out, flush=True)
print("If the 'go' command is not found, try reopening the terminal.", file=_gvm_std.out, flush=True)
return True, ""
@classmethod
def use(cls, bt_gvm_path, version) -> Tuple[bool, str]:
# Create a symbolic link
src = os.path.join(bt_gvm_path, version)
if not os.path.isdir(src):
return False, "{} file is missing.".format(version)
dst = "/usr/local/btgo"
if os.path.islink(dst):
os.unlink(dst)
os.symlink(src, dst)
cls.add_to_path()
time.sleep(0.1)
out, err = cls.test()
if version in out:
return True, ""
else:
return False, err
@staticmethod
def test() -> Tuple[str, str]:
# Execute the command and get the output
os.environ["PATH"] = "/usr/local/btgo/bin:" + os.environ["PATH"]
p = subprocess.Popen("go version", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env=os.environ)
output, err = p.communicate()
return output.decode(), err.decode()
@staticmethod
def add_to_path():
profile = "/etc/profile"
with open(profile, "r") as f:
profile_data = f.read()
if profile_data.find(template) != -1:
return
with open(profile, "a") as f:
f.write("\n" + template)
os.system("source {}".format(profile))
@staticmethod
def parse_version(data: str) -> Tuple[bool, str]:
v_rep = r"(?P<target>1\.\d{1,2}(\.\d{1,2})?)"
v_res = re.search(v_rep, data)
v = None
if v_res:
v = v_res.group("target")
return True, "go" + v
else:
return False, ""
class GVM(object):
stable_versions: Optional[List[GoVersion]] = None
history_versions: Optional[List[GoVersion]] = None
arch = ""
use_shell = False
bt_gvm_path = "/usr/local/btgojdk"
now_version = None
def __init__(self):
self.get_now_version()
if not os.path.exists(self.bt_gvm_path):
os.makedirs(self.bt_gvm_path)
self._cloud = None
@staticmethod
def check_use():
res = os.popen("lsattr /etc/profile")
return res.read().find("--i--") == -1
@property
def cloud(self):
if not isinstance(self._cloud, _Mirror):
self._cloud = get_cloud()
return self._cloud
def get_now_version(self) -> None:
now_version = "/usr/local/btgo"
if os.path.islink(now_version):
now_version = os.path.basename(os.readlink(now_version))
else:
now_version = None
self.now_version = now_version
def __new__(cls, *args, **kwargs) -> Union["GVM", str]:
arch, errmsg = get_arch()
if errmsg:
return errmsg
_gvm = super().__new__(cls, *args, **kwargs)
_gvm.arch = arch
return _gvm
# Get Go version
def get_go_version(self):
self.stable_versions, self.history_versions = self._get_versions_by_local()
if not self.history_versions:
if self.use_shell:
print("Local record file not found, requesting GO version data from the cloud. This may take some time, please wait.", file=_gvm_std.out)
self.stable_versions, self.history_versions = self._get_versions_by_cloud()
# Cache data locally
if isinstance(self.stable_versions, list):
self._save_cached(self.stable_versions, self.history_versions)
if not self.history_versions and isinstance(self.stable_versions, str):
raise GVMError("Network error:" + self.stable_versions)
return self.stable_versions, self.history_versions
def _get_versions_by_local(self) -> [Tuple[Optional[List[GoVersion]], Optional[List[GoVersion]]]]:
"""
Get cached data of local archived and stable versions
"""
local_path = "/www/server/panel/data/pygvm"
if not os.path.exists(local_path):
os.makedirs(local_path)
return None, None
stable_file = os.path.join(local_path, "stable_versions.txt")
history_file = os.path.join(local_path, "history_versions.txt")
if not os.path.isfile(stable_file) or not os.path.isfile(history_file):
return None, None
with open(stable_file, "r") as f:
_arch = f.readline().strip()
if _arch != self.arch:
return None, None
stable_versions = [GoVersion.deserialize(line.strip()) for line in f.readlines()]
with open(history_file, "r") as f:
_arch = f.readline().strip()
if _arch != self.arch:
return None, None
history_versions = [GoVersion.deserialize(line.strip()) for line in f.readlines()]
return stable_versions, history_versions
def _get_versions_by_cloud(self) -> Tuple[Union[List[GoVersion], str], Optional[List[GoVersion]]]:
"""
Get supported archived and stable versions from the cloud
"""
return self.cloud.get_versions_by_cloud(self.arch)
def _save_cached(self, stable_go_versions: List[GoVersion], history_go_versions: List[GoVersion]) -> None:
local_path = "/www/server/panel/data/pygvm"
if not os.path.exists(local_path):
os.makedirs(local_path)
with open(os.path.join(local_path, "stable_versions.txt"), "w") as f:
f.write(self.arch + "\n")
for go_v in stable_go_versions:
f.write(go_v.serialize() + "\n")
with open(os.path.join(local_path, "history_versions.txt"), "w") as f:
f.write(self.arch + "\n")
for go_v in history_go_versions:
f.write(go_v.serialize() + "\n")
@staticmethod
def del_cached():
local_path = "/www/server/panel/data/pygvm"
stable_file = os.path.join(local_path, "stable_versions.txt")
history_file = os.path.join(local_path, "history_versions.txt")
if os.path.isfile(stable_file):
os.remove(stable_file)
if os.path.isfile(history_file):
os.remove(history_file)
def api_ls(self) -> List[str]:
return [i.strip() for i in os.listdir(self.bt_gvm_path) if i.startswith("go1")]
def cmd_ls(self) -> None:
versions = [i.strip() for i in os.listdir(self.bt_gvm_path) if i.startswith("go1")]
if not versions:
print("No GO language SDK versions installed")
return
print("version: ")
for i in versions:
if i == self.now_version:
i += " <- Now use this version"
print(" " + i)
def api_ls_remote(self, is_all: bool) -> Tuple[Optional[List[GoVersion]], Optional[List[GoVersion]], Optional[str]]:
try:
self.get_go_version()
except GVMError as e:
return [], [], str(e)
if is_all:
self.stable_versions.sort(key=lambda x: int(x.version.split(".")[1]), reverse=True)
self.history_versions.sort(key=lambda x: int(x.version.split(".")[1]), reverse=True)
return self.stable_versions, self.history_versions, None
stable_v = [".".join(i.version.split(".")[:2]) for i in self.stable_versions]
res_history = {}
for i in self.history_versions:
parse_list = i.version.split(".")
v = ".".join(parse_list[:2])
if len(parse_list) > 2:
# Patch version
three_idx = int(parse_list[2])
else:
three_idx = 0
# Skip if already exists in stable versions
if v in stable_v:
continue
if v in res_history:
if three_idx > res_history[v][0]:
res_history[v] = [three_idx, i]
else:
res_history[v] = [three_idx, i]
res_h = []
for i in res_history.values():
res_h.append(i[1])
res_h.sort(key=lambda x: int(x.version.split(".")[1]), reverse=True)
return self.stable_versions, res_h, None
def cmd_ls_remote(self, is_all: bool) -> None:
stable, history, err = self.api_ls_remote(is_all)
installed = self.api_ls()
if err:
print("Error occurred while obtaining version information", file=sys.stderr)
print(err, file=sys.stderr)
print("Stable Version:")
for i in stable:
if i.version in installed:
i.version += " <- installed"
print(" " + i.version)
print("History Version:")
for i in history:
if i.version in installed:
i.version += " <- installed"
print(" " + i.version)
def _get_version(self, version) -> Union[GoVersion, str]:
stable, history, err = self.api_ls_remote(True)
if err:
if self.use_shell:
print("Error occurred while obtaining version information", file=_gvm_std.err)
print(err, file=_gvm_std.err)
return err
for i in stable:
if i.version == version:
return i
for i in history:
if i.version == version:
return i
if self.use_shell:
print("Version not found", file=_gvm_std.err)
return "Version not found"
def api_use(self, version) -> Tuple[bool, str]:
versions = self.api_ls()
if version not in versions:
return False, "Version not found"
res = GoVersion.use(self.bt_gvm_path, version)
self.get_now_version()
return res
def cmd_use(self, version) -> None:
flag, err = self.api_use(version)
if flag:
print("Switched to: {}".format(version))
print("If the 'go' command is not found, try reopening the terminal.")
else:
print(err, file=sys.stderr)
def api_install(self, version) -> Tuple[bool, str]:
go_v = self._get_version(version)
if isinstance(go_v, str):
return False, go_v
go_v.set_cloud(self.cloud)
return go_v.install()
def cmd_install(self, version) -> None:
go_v = self._get_version(version)
go_v.set_cloud(self.cloud)
if isinstance(go_v, str):
pass
_, err = go_v.install()
if err:
print(err, file=sys.stderr)
def api_uninstall(self, version: str) -> Tuple[bool, str]:
versions = self.api_ls()
if version not in versions:
return False, "Version not found"
next_version = None
for i in versions:
if i != version:
next_version = i
break
if next_version is not None and version == self.now_version:
GoVersion.use(self.bt_gvm_path, next_version)
target_dir = os.path.join(self.bt_gvm_path, version)
if os.path.exists:
shutil.rmtree(target_dir, ignore_errors=True)
return True, ""
def cmd_uninstall(self, version: str) -> None:
flag, err = self.api_uninstall(version)
if flag:
print("Successfully uninstalled: {}".format(version))
else:
print(err, file=sys.stderr)
def cmd_clean_cache(self) -> None:
local_path = "/www/server/panel/data/pygvm"
for i in ("stable_versions.txt", "history_versions.txt"):
if os.path.isfile(local_path + "/" + i):
os.remove(local_path + "/" + i)
cache_dir = os.path.join(self.bt_gvm_path, "cached")
if os.path.isdir(cache_dir):
shutil.rmtree(cache_dir)
return
@staticmethod
def set_goproxy(proxy: str) -> bool:
import public
if os.path.isfile("/usr/local/btgo/bin/go"):
try:
subprocess.check_output([
"/usr/local/btgo/bin/go", "env", "-w", "GOPROXY=" + proxy, "GO111MODULE=on"
])
return True
except Exception:
pass
return False
@staticmethod
def get_goproxy() -> str:
if os.path.isfile("/usr/local/btgo/bin/go"):
try:
out = subprocess.check_output(["/usr/local/btgo/bin/go", "env", "GOPROXY"])
if out:
return out.decode("utf-8").strip()
except:
pass
return ""
@staticmethod
def set_std(out: FileIO, err: FileIO) -> None:
_gvm_std.out = out
_gvm_std.err = err
pygvm = GVM()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='gvm Go Language SDK Manager')
# Add subcommands
subparsers = parser.add_subparsers(title='operation', dest='command')
# Add ls subcommand
subparsers.add_parser('ls', help='Display the installed version of the Go language SDK')
# Add clean-cache subcommand
subparsers.add_parser('clean-cache', help='Clear cache')
# Add ls-remote subcommand
parser_ls_r = subparsers.add_parser('ls-remote', help='Display the versions that can install the Go language SDK, and by default only display the newer versions in each version')
parser_ls_r.add_argument('-a', action='store_true', help='Show all Go language SDK versions that can be installed')
# Add use subcommand
parser_use = subparsers.add_parser('use', help='Use specified version')
parser_use.add_argument('use_param', type=str, help='Complete version number')
# Add install subcommand
parser_install = subparsers.add_parser('install', help='Install the specified version')
parser_install.add_argument('install_param', type=str, help='Complete version number')
# Add uninstall subcommand
parser_uninstall = subparsers.add_parser('uninstall', help='Uninstall and delete the specified version')
parser_uninstall.add_argument('uninstall_param', type=str, help='Complete version number')
input_args = parser.parse_args()
gvm = GVM()
if isinstance(gvm, str):
print(gvm, file=sys.stderr)
exit(1)
gvm.use_shell = True
if input_args.command == 'ls':
gvm.cmd_ls()
elif input_args.command == "ls-remote":
_is_all = True if input_args.a else False
gvm.cmd_ls_remote(_is_all)
elif input_args.command == "use":
_flag, _v = GoVersion.parse_version(input_args.use_param)
if _flag:
gvm.cmd_use(_v)
else:
print("Version parameter error, it should be the structure of 1.xx.xx", file=sys.stderr)
elif input_args.command == "install":
_flag, _v = GoVersion.parse_version(input_args.install_param)
if _flag:
gvm.cmd_install(_v)
else:
print("Version parameter error, it should be the structure of 1.xx.xx", file=sys.stderr)
elif input_args.command == "uninstall":
_flag, _v = GoVersion.parse_version(input_args.uninstall_param)
if _flag:
gvm.cmd_uninstall(_v)
else:
print("Version parameter error, it should be the structure of 1.xx.xx", file=sys.stderr)
elif input_args.command == "clean-cache":
gvm.cmd_clean_cache()
else:
print("Use pygvm - h to view operation instructions", file=sys.stderr)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+43 -8
View File
@@ -759,9 +759,34 @@ export PATH
self.install_packages(get)
self.start_project(get)
return_message=public.return_data(True,'Added project successfully',project_id)
# ================ git start ======================
try:
if get.get('deploy_type') in ['ssh', 'github']:
if get.get('deploy_type') == 'ssh':
from git_tools import GitTools
git_obj = GitTools()
# 已clone项目,使用.git导入
res = git_obj.get_git_directory(public.to_dict_obj({"site_id":project_id}))
if res['status'] != 0:
return public.return_message(-1, 0, res['message'])
res = res['message']
res = git_obj.import_existing_repository(public.to_dict_obj({"site_id":project_id, "repo" : res['repo'],
"branch": res['branch'],"key_path":res['key_path'], "project_type":'node'}))
if res['status'] != 0:
return public.return_message(-1, 0, res['message'])
except Exception as e:
print(e)
# 失败不删除项目
return public.return_message(-1, 0, public.lang("There was an error while configuring Git tools!"))
# ================ git end ======================
del return_message['status']
return public.return_message(0,0, return_message)
def modify_project(self,get):
'''
@name 修改指定项目
@@ -949,7 +974,6 @@ export PATH
try:
project['project_config'] = json.loads(project['project_config'])
p_type = 'pm2' if project['project_config'].get('pm2_name') else 'nodejs'
res = None
if operation_type == 'delete':
temp_get = public.to_dict_obj({'project_name': project['name'],'project_type': p_type,'pm2_name':project['project_config'].get('pm2_name')})
res = comMod.main().delete(temp_get)
@@ -957,12 +981,18 @@ export PATH
res = comMod.main().set_project_status(public.to_dict_obj(
{"project_name": project['name'], "project_type": p_type, "status": operation_type,
"pm2_name": project['project_config'].get('pm2_name'),"run_user":project['project_config'].get('run_user')}))
if res is None or res['status'] != 0:
if res['status'] != 0:
msg_list.append({'name': project['name'],'status':False,'msg':res['message']['result']})
else:
success_count += 1
msg_list.append({'name': project['name'],'status':True, 'msg': res['message']['result']})
if operation_type == 'delete':
# 删除git
from git_tools import GitTools
GitTools().del_site_git(public.to_dict_obj({'site_id': project['id']}))
except Exception as e:
msg_list.append({'name': project['name'], 'status': False, 'msg': str(e)})
@@ -1642,11 +1672,14 @@ export PATH
for i in self._pids:
try:
p = psutil.Process(i)
except: continue
if p.ppid() == pid:
if i in project_pids: continue
if p.name() in ['bash']: continue
project_pids.append(i)
if p.status() == "zombie":
continue
if p.ppid() == pid:
if i in project_pids:
continue
project_pids.append(i)
except:
continue
other_pids = []
for i in project_pids:
@@ -2299,6 +2332,8 @@ cd {}
return public.return_message(0,0,False)
data=public.readFile(pid_file)
if isinstance(data,str) and data:
if not data.isdigit():
return public.return_message(0,0,False)
pid = int(data)
pids = self.get_project_pids(pid=pid, without_request=True)
else:
+6 -3
View File
@@ -77,8 +77,11 @@ def _init_ln_gvm() -> None:
pass
_init_ln_gvm()
EnvironmentReporter().init_report()
try:
_init_ln_gvm()
del _init_ln_gvm
except Exception:
pass
class main(projectBase):
@@ -1621,7 +1624,7 @@ echo $! > {pid_file}'''.format(
block = ''
nl_count = 0
start = 0
fsock = open(filename, 'rU')
fsock = open(filename, 'r')
try:
fsock.seek(0, 2)
curpos = fsock.tell()
+303
View File
@@ -0,0 +1,303 @@
# coding: utf-8
# -------------------------------------------------------------------
# aapanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# -------------------------------------------------------------------
# Author: baozi <baozi@bt.cn>
# -------------------------------------------------------------------
# Project monitoring restart module
# ------------------------------
# Function: Monitor specified project files, and restart the service when files change
# 1. Configuration
# conf = {
# "project_type": "java",
# "project_id": 14,
# "project_name": "project_name",
# "watch_path": "/www/wwwroot/java_p/xxx.jar",
# }
# File handling:
# 1. Compare size
# 2. Compare mtime
# Directory handling: Todo: Develop as needed
# 1. Not supported yet
# Todo: Currently only supports Java Spring Boot projects
# Service operation logic
# BT-Task polling
# 1.1 Load configuration file
# 1.2 Get status and compare
# 1.3 Handle service restart based on comparison results
# 1.4 Attempt to reload configuration file
import threading
import json
import os.path
import time
import sys
from importlib import import_module
from typing import Optional, Dict, Any, Tuple
os.chdir("/www/server/panel")
if "class/" not in sys.path:
sys.path.insert(0, "class/")
if "/www/server/panel" not in sys.path:
sys.path.insert(0, "/www/server/panel")
import public
class WatchConfig:
_CONF_FILE = "{}/data/watch_project.json".format(public.get_panel_path())
def __init__(self):
self._config: Optional[dict] = None
self._conf_time: Optional[int] = None
@property
def config(self) -> dict:
if self._config is not None:
return self._config
data = {}
if not os.path.isfile(self._CONF_FILE):
self._config = data
self.save_conf()
return self._config
try:
data = json.loads(public.readFile(self._CONF_FILE))
if isinstance(data, dict):
self._config = data
self._conf_time = int(os.path.getmtime(self._CONF_FILE)) # Save timestamp when correctly reading configuration
else:
self._config = {}
except (json.JSONDecodeError, TypeError):
self._config = {}
if self._conf_time is None:
self.save_conf()
return self._config
def save_conf(self):
if self._config is not None:
public.writeFile(self._CONF_FILE, json.dumps(self._config))
self._conf_time = int(os.path.getmtime(self._CONF_FILE))
def reload(self) -> bool:
if not os.path.exists(self._CONF_FILE):
self._config = {}
self.save_conf()
return True
now_mtime = int(os.path.getmtime(self._CONF_FILE))
if now_mtime == self._conf_time:
return False
else:
self._config = None
return True
class Task:
WAIT_TIME = 20
def __init__(self):
self.watch_conf = WatchConfig()
self._mod_cache: Dict[str, Any] = {}
self._status_cache = {}
self._sub_thread_cache = {}
def get_mod_obj(self, project_type: str) -> Any:
if project_type in self._mod_cache:
return self._mod_cache[project_type]
try:
if project_type == "java" and os.path.exists("/www/server/panel/mod/project/java/projectMod.py"):
main_module = import_module("mod.project.java.projectMod")
else:
if not project_type.endswith("Model"):
project_type += "Model"
main_module = import_module(".{}".format(project_type), package="projectModel")
except ImportError:
print(public.get_error_info())
# public.print_log(public.get_error_info())
return None
main_class = getattr(main_module, "main", None)
if not callable(main_class):
return None
self._mod_cache[project_type] = main_class()
return self._mod_cache[project_type]
def need_restart(self, project_type: str, project_id: int) -> bool:
model_main_obj = self.get_mod_obj(project_type)
if not model_main_obj:
return False
stop_by_user = getattr(model_main_obj, "is_stop_by_user")(project_id)
if stop_by_user:
return False
return True
def get_status_by_name(self, project_name: str) -> Tuple[int, float]:
conf = self.watch_conf.config[project_name]
file = conf['watch_path']
if not os.path.exists(file):
return 0, 0
return os.path.getsize(file), int(os.path.getmtime(file))
def check_status(self, project_name: str) -> bool:
if project_name not in self._status_cache:
self._status_cache[project_name] = self.get_status_by_name(project_name)
return False
now_status = self.get_status_by_name(project_name)
if self._status_cache[project_name] == now_status:
return False
else:
self._status_cache[project_name] = now_status
return True
def restart_with_threading(self, project_type, project_name):
try:
model_main_obj = self.get_mod_obj(project_type)
get_obj = public.dict_obj()
get_obj.project_name = project_name
getattr(model_main_obj, "restart_project")(get_obj)
except:
# print(public.get_error_info())
public.print_log(public.get_error_info())
def _run(self):
while True:
# print(self.watch_conf.config)
# print(self.watch_conf._conf_time)
# print(self._status_cache)
for p_name, p_conf in self.watch_conf.config.items():
p_type = p_conf["project_type"]
if self.need_restart(p_type, p_conf["project_id"]):
if self.check_status(p_name):
if p_name in self._sub_thread_cache:
self._sub_thread_cache[p_name].join()
restart_task = threading.Thread(target=self.restart_with_threading, args=(p_type, p_name))
restart_task.start()
self._sub_thread_cache[p_name] = restart_task
# public.print_log("---------WAIT--------------")
for i in range(int(self.WAIT_TIME / 5)):
time.sleep(5)
if self.watch_conf.reload():
print("Configuration file changed, reloading configuration")
# public.print_log("Configuration file changed, reloading configuration")
# public.print_log(self.watch_conf.config)
break
def run(self):
try:
self._run()
except KeyboardInterrupt:
return
except:
public.print_log(public.get_error_info())
# print(self.watch_conf.config)
self.watch_conf = WatchConfig()
self.run()
class main:
def __init__(self):
pass
@staticmethod
def project_is_watch(get):
try:
project_name = get.project_name.strip()
except AttributeError:
return public.returnMsg(False, "Parameter error")
conf = WatchConfig()
if project_name in conf.config:
return conf.config[project_name]
else:
return {}
@staticmethod
def add_project_watch(get):
try:
project_name = get.project_name.strip()
except AttributeError:
return public.returnMsg(False, "Parameter error")
site_info = public.M('sites').where("name = ?", (project_name, )).find()
if isinstance(site_info, str) and site_info.startswith("error"):
return public.returnMsg(False, "Database query error:" + site_info)
if not isinstance(site_info, dict):
return public.returnMsg(False, "Specified website not found")
if site_info["project_type"] != "Java":
return public.returnMsg(False, "Currently only supports Java Springboot projects")
project_config = json.loads(site_info["project_config"])
if project_config['java_type'] != 'springboot':
return public.returnMsg(False, "Currently only supports Java Springboot projects")
conf = WatchConfig()
conf.config[project_name] = {
"project_type": site_info["project_type"].lower(),
"project_id": site_info["id"],
"project_name": project_name,
"watch_path": project_config["project_jar"]
}
conf.save_conf()
return public.returnMsg(True, "Added successfully")
@staticmethod
def del_project_watch(get):
try:
project_name = get.project_name.strip()
except AttributeError:
return public.returnMsg(False, "Parameter error")
conf = WatchConfig()
if project_name in conf.config:
del conf.config[project_name]
conf.save_conf()
return public.returnMsg(True, "Deleted successfully")
def use_project_watch(project_name: str) -> bool:
conf = WatchConfig()
return project_name in conf.config
def add_project_watch(p_name: str, p_type: str, site_id: int, watch_path: str) -> None:
conf = WatchConfig()
conf.config[p_name] = {
"project_type": p_type.lower(),
"project_id": site_id,
"project_name": p_name,
"watch_path": watch_path
}
try:
site_ids = public.M('sites').field("id").select()
site_id_list = [i['id'] for i in site_ids]
public.print_log(site_id_list)
remove_list = []
for k, v in conf.config.items():
if v["project_id"] not in site_id_list:
remove_list.append(k)
for i in remove_list:
del conf.config[i]
except:
pass
conf.save_conf()
def del_project_watch(p_name: str) -> None:
conf = WatchConfig()
if p_name in conf.config:
del conf.config[p_name]
conf.save_conf()
if __name__ == '__main__':
Task().run()