From b49a7129ff4580ee2e4a97863f0e7cf3dba840f1 Mon Sep 17 00:00:00 2001 From: shumengya <3205788256@qq.com> Date: Tue, 23 Sep 2025 12:33:50 +0800 Subject: [PATCH] update --- .../__pycache__/changesource.cpython-310.pyc | Bin 0 -> 16116 bytes Linux脚本/changesource.py | 657 ++++++++++++++++++ Linux脚本/systeminfo.py | 234 +++++++ 换源面板 | 13 + 4 files changed, 904 insertions(+) create mode 100644 Linux脚本/__pycache__/changesource.cpython-310.pyc create mode 100644 Linux脚本/changesource.py create mode 100644 Linux脚本/systeminfo.py create mode 100644 换源面板 diff --git a/Linux脚本/__pycache__/changesource.cpython-310.pyc b/Linux脚本/__pycache__/changesource.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf0546453878c417cb0f2f558c0fd174e592b24f GIT binary patch literal 16116 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 OED8L1B#QLl>;D(qAhBKm literal 0 HcmV?d00001 diff --git a/Linux脚本/changesource.py b/Linux脚本/changesource.py new file mode 100644 index 0000000..09d708b --- /dev/null +++ b/Linux脚本/changesource.py @@ -0,0 +1,657 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +换源脚本:支持以下发行版与架构 +- Debian 11 (bullseye), Debian 12 (bookworm) — amd64/arm64 +- Ubuntu 22.04 (jammy), Ubuntu 24.04 (noble) — amd64/arm64 + +APT 镜像选项:official(官方)、aliyun(阿里云)、tsinghua(清华) + +pip 永久换源选项:tsinghua、aliyun、tencent、douban、default(恢复官方) + +用法示例: + APT 交互: sudo python3 changesource.py + APT 指定: sudo python3 changesource.py --mirror aliyun + APT 仅查看: python3 changesource.py --mirror tsinghua --dry-run + APT 写入更新: sudo python3 changesource.py --mirror official --update + + pip 交互: python3 changesource.py --pip-only + pip 指定: python3 changesource.py --pip-mirror tsinghua + pip 恢复默认: python3 changesource.py --pip-mirror default + +注意:写入 /etc/apt/sources.list 与执行 apt-get update 需要 root 权限;Termux 下不需要 root。 +""" + +from __future__ import annotations + +import argparse +import datetime +import os +import platform +import re +import shutil +import subprocess +import sys +from typing import Dict, Tuple, List + + +SUPPORTED = { + "debian": {"11": "bullseye", "12": "bookworm"}, + "ubuntu": {"22.04": "jammy", "24.04": "noble"}, +} + + +# Termux 检测 +def is_termux() -> bool: + prefix = os.environ.get("PREFIX", "") + if "com.termux" in prefix: + return True + if os.environ.get("TERMUX_VERSION"): + return True + # 兜底:常见默认 PREFIX + if prefix == "/data/data/com.termux/files/usr": + return True + return False + +def get_termux_prefix() -> str: + return os.environ.get("PREFIX", "/data/data/com.termux/files/usr") + + +#阅读系统信息 +def read_os_release() -> Dict[str, str]: + data: Dict[str, str] = {} + try: + with open("/etc/os-release", "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + k, v = line.split("=", 1) + v = v.strip().strip('"').strip("'") + data[k] = v + except Exception: + pass + return data + +#规范化架构名称 +def normalize_arch(uname_arch: str) -> str: + a = uname_arch.lower() + if a in ("x86_64", "amd64"): + return "amd64" + if a in ("aarch64", "arm64"): + return "arm64" + # 其它架构暂不写入 sources 约束(APT 不必声明架构),但用于提示 + return a + + +#获取发行版版本信息 +def get_distro_info() -> Tuple[str, str, str]: + """返回 (id, version_id, codename) + id: debian/ubuntu + version_id: 如 '11', '12', '22.04', '24.04' + codename: bullseye/bookworm/jammy/noble + """ + info = read_os_release() + distro_id = info.get("ID", "").lower() + version_id = info.get("VERSION_ID", "") + codename = info.get("VERSION_CODENAME", "").lower() + + # 一些派生可能提供 UBUNTU_CODENAME + if not codename: + codename = info.get("UBUNTU_CODENAME", "").lower() + + # 规范化版本格式 + if distro_id == "debian": + # Debian 通常为 '11' 或 '12' + version_id = version_id.split(".")[0] + if not codename and version_id in SUPPORTED["debian"]: + codename = SUPPORTED["debian"][version_id] + elif distro_id == "ubuntu": + # Ubuntu 保留小版本以匹配 22.04 / 24.04 + m = re.match(r"(\d{2})\.(\d{2})", version_id or "") + if m: + version_id = f"{m.group(1)}.{m.group(2)}" + if not codename and version_id in SUPPORTED["ubuntu"]: + codename = SUPPORTED["ubuntu"][version_id] + + return distro_id, version_id, codename + +#验证发行版与版本支持 +def validate_supported(distro_id: str, version_id: str, codename: str) -> Tuple[bool, str]: + if distro_id not in SUPPORTED: + return False, f"不支持的发行版: {distro_id}" + if distro_id == "debian": + if version_id not in SUPPORTED[distro_id]: + return False, f"不支持的 Debian 版本: {version_id}(仅支持 11/12)" + expect = SUPPORTED[distro_id][version_id] + if codename != expect: + return False, f"版本代号不匹配: 期望 {expect}, 实际 {codename or '未知'}" + elif distro_id == "ubuntu": + if version_id not in SUPPORTED[distro_id]: + return False, f"不支持的 Ubuntu 版本: {version_id}(仅支持 22.04/24.04)" + expect = SUPPORTED[distro_id][version_id] + if codename != expect: + return False, f"版本代号不匹配: 期望 {expect}, 实际 {codename or '未知'}" + return True, "" + + +# ---- Termux 支持(仅清华源)---- +def _termux_apply_mirror_to_file(path: str, pattern: str, new_line: str, dry_run: bool) -> bool: + """在给定文件中,将匹配 pattern 的行注释掉并在下一行追加 new_line;如果未匹配,且文件存在且不包含 new_line,则在末尾追加。 + 返回是否发生变更。 + """ + if not os.path.exists(path): + return False + + try: + with open(path, "r", encoding="utf-8") as f: + lines: List[str] = f.read().splitlines() + except Exception: + # 读取失败视为不变更 + return False + + import re as _re + changed = False + out_lines: List[str] = [] + matched_once = False + for line in lines: + m = _re.match(pattern, line) + if m: + matched_once = True + if not dry_run: + out_lines.append("#" + line) + out_lines.append(new_line) + changed = True + else: + out_lines.append(line) + + if not matched_once: + # 未匹配时,如果没有新行,则追加 + if new_line not in lines: + if not dry_run: + if out_lines and out_lines[-1].strip(): + out_lines.append("") + out_lines.append("# added by changesource.py") + out_lines.append(new_line) + changed = True + + if changed and not dry_run: + # 备份并写回 + backup_file(path) + with open(path, "w", encoding="utf-8") as f: + f.write("\n".join(out_lines) + "\n") + + return changed + +# Termux 切换到清华源 +def termux_switch_to_tsinghua(dry_run: bool, update: bool, assume_yes: bool) -> int: + prefix = get_termux_prefix() + main_path = os.path.join(prefix, "etc/apt/sources.list") + x11_path = os.path.join(prefix, "etc/apt/sources.list.d/x11.list") + root_path = os.path.join(prefix, "etc/apt/sources.list.d/root.list") + + # 与清华源教程一致的替换模式 + main_pat = r"^(deb.*stable main)$" + main_new = "deb https://mirrors.tuna.tsinghua.edu.cn/termux/apt/termux-main stable main" + + x11_pat = r"^(deb.*x11 main)$" + x11_new = "deb https://mirrors.tuna.tsinghua.edu.cn/termux/apt/termux-x11 x11 main" + + root_pat = r"^(deb.*root main)$" + root_new = "deb https://mirrors.tuna.tsinghua.edu.cn/termux/apt/termux-root root main" + + print("[Termux] 目标镜像:清华 TUNA") + print(f"[Termux] PREFIX={prefix}") + + # 确认 + if not dry_run and not assume_yes: + ans = input("确认为 Termux 写入清华镜像源?[y/N]: ").strip().lower() + if ans not in ("y", "yes"): + print("已取消。") + return 0 + + + # 执行替换 + changed_any = False + for p, pat, newl in [ + (main_path, main_pat, main_new), + (x11_path, x11_pat, x11_new), + (root_path, root_pat, root_new), + ]: + if os.path.exists(p): + print(f"[Termux] 处理 {p}") + changed = _termux_apply_mirror_to_file(p, pat, newl, dry_run) + changed_any = changed_any or changed + else: + print(f"[Termux] 跳过(不存在):{p}") + + if dry_run: + print("[Termux] dry-run: 未实际写入。") + return 0 + + if update: + # 运行 apt update / upgrade + try: + rc1 = subprocess.run(["apt", "update"], check=False).returncode + if rc1 != 0: + print(f"apt update 失败,返回码 {rc1}") + return rc1 + cmd = ["apt", "upgrade"] + if assume_yes: + cmd.insert(2, "-y") + rc2 = subprocess.run(cmd, check=False).returncode + if rc2 != 0: + print(f"apt upgrade 失败,返回码 {rc2}") + return rc2 + except FileNotFoundError: + print("未找到 apt,请确认处于 Termux 环境。", file=sys.stderr) + return 127 + + print("[Termux] 已完成清华源处理。") + return 0 + + +#渲染 Debian 源列表 +def render_debian_sources(codename: str, mirror: str) -> str: + # 组件:Debian 12 含 non-free-firmware + components = "main contrib non-free" + if codename == "bookworm": + components = "main contrib non-free non-free-firmware" + + if mirror == "official": + base = "http://deb.debian.org/debian" + sec = "http://security.debian.org/debian-security" + elif mirror == "aliyun": + base = "https://mirrors.aliyun.com/debian" + sec = "https://mirrors.aliyun.com/debian-security" + elif mirror == "tsinghua": + base = "https://mirrors.tuna.tsinghua.edu.cn/debian" + sec = "https://mirrors.tuna.tsinghua.edu.cn/debian-security" + else: + raise ValueError(f"未知镜像: {mirror}") + + lines = [ + f"deb {base} {codename} {components}", + f"deb {sec} {codename}-security {components}", + f"deb {base} {codename}-updates {components}", + ] + return "\n".join(lines) + "\n" + +#渲染 Ubuntu 源列表 +def render_ubuntu_sources(codename: str, mirror: str) -> str: + if mirror == "official": + base = "http://archive.ubuntu.com/ubuntu" + sec = "http://security.ubuntu.com/ubuntu" + elif mirror == "aliyun": + base = sec = "https://mirrors.aliyun.com/ubuntu" + elif mirror == "tsinghua": + base = sec = "https://mirrors.tuna.tsinghua.edu.cn/ubuntu" + else: + raise ValueError(f"未知镜像: {mirror}") + + components = "main restricted universe multiverse" + lines = [ + f"deb {base} {codename} {components}", + f"deb {base} {codename}-updates {components}", + f"deb {base} {codename}-backports {components}", + f"deb {sec} {codename}-security {components}", + ] + return "\n".join(lines) + "\n" + +#根据发行版渲染源列表 +def render_sources(distro_id: str, codename: str, mirror: str) -> str: + if distro_id == "debian": + return render_debian_sources(codename, mirror) + elif distro_id == "ubuntu": + return render_ubuntu_sources(codename, mirror) + else: + raise ValueError(f"不支持的发行版: {distro_id}") + +#确保以 root 权限运行 +def ensure_root(for_what: str) -> None: + if os.geteuid() != 0: + print(f"[需要 root] {for_what} 请使用 sudo 运行此脚本。", file=sys.stderr) + sys.exit(1) + +#备份文件 +def backup_file(path: str) -> str: + ts = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + bak = f"{path}.bak-{ts}" + try: + if os.path.exists(path): + shutil.copy2(path, bak) + except Exception as e: + print(f"备份 {path} 失败: {e}", file=sys.stderr) + sys.exit(1) + return bak + +#写入 sources.list +def write_sources(content: str, path: str = "/etc/apt/sources.list") -> None: + # 先备份,再写入 + backup_file(path) + try: + with open(path, "w", encoding="utf-8") as f: + f.write(content) + except Exception as e: + print(f"写入 {path} 失败: {e}", file=sys.stderr) + sys.exit(1) + +#执行 apt-get update +def apt_update() -> int: + try: + # 与用户共享输出 + proc = subprocess.run(["apt-get", "update"], check=False) + return proc.returncode + except FileNotFoundError: + print("未找到 apt-get,请确认系统为 Debian/Ubuntu。", file=sys.stderr) + return 127 + +#交互式选择镜像 +def choose_mirror_interactive() -> str: + print("\n================ Linux 软件源镜像切换 ================") + print("请选择要切换的镜像源:") + options = [ + ("official", "默认官方源"), + ("aliyun", "阿里云"), + ("tsinghua", "清华源"), + ] + print(" 0. 跳过(不更改)") + for idx, (_, label) in enumerate(options, start=1): + print(f" {idx}. {label}") + raw = input("输入编号 (默认 1): ").strip() + if not raw: + return options[0][0] + try: + i = int(raw) + if i == 0: + return "skip" + if 1 <= i <= len(options): + return options[i - 1][0] + except Exception: + pass + print("输入无效,默认选择 1. 默认官方源。") + return options[0][0] + +# 统一交互主面板 +def show_main_menu() -> tuple[str, str | None]: + """ + 显示统一交互面板并返回用户选择。 + 返回 (mode, value) + - mode: 'apt' 或 'pip' 或 'skip' + - value: 对于 apt,为 'ubuntu'|'debian'|'termux';对于 pip,为 'tsinghua'|'aliyun'|'tencent'|'default' + """ + print("============请选择需要换源的命令============") + print("Linux发行版软件源:") + print("1.Ubuntu(支持22 24 amd64 arm64 官方源 清华源 阿里源)") + print("2.Debian(支持11 12 amd64 arm64 官方源 清华源 阿里源)") + print("3.Termux(支持清华源)") + print() + print("Python的pip镜像源:") + print("a.清华源") + print("b.阿里源") + print("c.腾讯源") + print("d.官方源") + print("===========================================") + + sel = input("请输入选项编号(1/2/3 或 a/b/c/d,其他跳过):").strip().lower() + if sel == "1": + return ("apt", "ubuntu") + if sel == "2": + return ("apt", "debian") + if sel == "3": + return ("apt", "termux") + if sel == "a": + return ("pip", "tsinghua") + if sel == "b": + return ("pip", "aliyun") + if sel == "c": + return ("pip", "tencent") + if sel == "d": + return ("pip", "default") + return ("skip", None) + +#解析命令行参数 +def parse_args() -> argparse.Namespace: + p = argparse.ArgumentParser(description="APT 换源脚本") + p.add_argument("--mirror", choices=["official", "aliyun", "tsinghua"], help="选择镜像源") + p.add_argument("--dry-run", action="store_true", help="仅打印将要写入的 sources.list,不实际写入") + p.add_argument("--update", action="store_true", help="写入后执行 apt-get update") + p.add_argument("-y", "--yes", action="store_true", help="不提示,直接写入") + p.add_argument("--path", default="/etc/apt/sources.list", help="sources.list 路径(默认 /etc/apt/sources.list)") + # pip 相关 + p.add_argument("--pip-mirror", choices=["tsinghua", "aliyun", "tencent", "douban", "default"], help="设置 pip 全局镜像(default 为恢复官方)") + p.add_argument("--pip-only", action="store_true", help="仅执行 pip 换源,不进行 APT 换源") + return p.parse_args() + + +# pip 镜像映射 +PIP_MIRRORS: Dict[str, str | None] = { + "tsinghua": "https://pypi.tuna.tsinghua.edu.cn/simple", + "aliyun": "https://mirrors.aliyun.com/pypi/simple/", + "tencent": "http://mirrors.cloud.tencent.com/pypi/simple", + "douban": "http://pypi.douban.com/simple/", + "default": None, # unset +} + + +def choose_pip_mirror_interactive() -> str: + print("\n=============== Python 的 pip 镜像源切换 ===============") + print("请选择 pip 镜像:") + options = [ + ("tsinghua", "清华 TUNA"), + ("aliyun", "阿里云"), + ("tencent", "腾讯云"), + ("douban", "豆瓣"), + ("default", "恢复官方默认"), + ] + print(" 0. 跳过(不更改)") + for i, (_, label) in enumerate(options, 1): + print(f" {i}. {label}") + raw = input("输入编号 (默认 1): ").strip() + if not raw: + return options[0][0] + try: + idx = int(raw) + if idx == 0: + return "skip" + if 1 <= idx <= len(options): + return options[idx - 1][0] + except Exception: + pass + print("输入无效,默认选择 1. 清华 TUNA。") + return options[0][0] + + +def run_pip_config(mirror_key: str, dry_run: bool, assume_yes: bool) -> int: + url = PIP_MIRRORS.get(mirror_key) + py = sys.executable or "python3" + if url: + cmd = [py, "-m", "pip", "config", "set", "global.index-url", url] + desc = f"pip 使用镜像: {mirror_key} -> {url}" + else: + cmd = [py, "-m", "pip", "config", "unset", "global.index-url"] + desc = "pip 恢复官方默认源" + print(f"[pip] {desc}") + print(f"[pip] 将执行: {' '.join(cmd)}") + if dry_run: + print("[pip] dry-run: 未实际执行。") + return 0 + if not assume_yes: + ans = input("确认执行 pip 配置变更?[y/N]: ").strip().lower() + if ans not in ("y", "yes"): + print("已取消。") + return 0 + try: + rc = subprocess.run(cmd, check=False).returncode + if rc == 0: + print("[pip] 已完成。") + else: + print(f"[pip] 失败,返回码 {rc}") + return rc + except Exception as e: + print(f"[pip] 执行失败: {e}", file=sys.stderr) + return 1 + +#主函数 +def main() -> None: + args = parse_args() + + distro_id, version_id, codename = get_distro_info() + arch = normalize_arch(platform.machine()) + + # 在换源前输出系统信息 + print(f"检测到系统:distro={distro_id or 'unknown'}, version={version_id or 'unknown'}, codename={codename or 'unknown'}, arch={arch}") + + # 两个板块:APT 与 pip + final_rc = 0 + + # 如果未提供任何镜像参数,则进入统一交互主面板一次 + invoked_by_menu = False + if not any([args.mirror, args.pip_mirror, args.pip_only]): + mode, value = show_main_menu() + invoked_by_menu = True + if mode == "apt": + # 将菜单选择映射到 apt 的 mirror 与环境 + if value == "termux": + # Termux 只支持清华 + args.mirror = "tsinghua" + # 用户意图仅为 APT,故跳过后续 pip 交互 + args.pip_mirror = "skip" + elif mode == "pip": + # 直接设置 pip 目标镜像并跳过 APT 流程 + args.pip_mirror = value + args.pip_only = True + else: + # 完全跳过:同时跳过 APT 与 pip 的后续交互 + print("已跳过。") + args.mirror = "skip" + args.pip_mirror = "skip" + + # APT 板块 + if not args.pip_only: + mirror = args.mirror or choose_mirror_interactive() + if mirror != "skip": + if is_termux(): + if mirror != "tsinghua": + print("Termux 环境当前仅支持切换到清华源(tsinghua)。请使用 --mirror tsinghua 或选择跳过。", file=sys.stderr) + final_rc = final_rc or 2 + else: + rc = termux_switch_to_tsinghua(args.dry_run, args.update, args.yes) + final_rc = rc or final_rc + else: + ok, reason = validate_supported(distro_id, version_id, codename) + if not ok: + print(reason, file=sys.stderr) + final_rc = final_rc or 2 + else: + try: + content = render_sources(distro_id, codename, mirror) + except Exception as e: + print(str(e), file=sys.stderr) + sys.exit(3) + + header = ( + f"# Generated by changesource.py on {datetime.datetime.now().isoformat(timespec='seconds')}\n" + f"# distro={distro_id} version={version_id} codename={codename} arch={arch}\n" + f"# mirror={mirror}\n" + ) + content = header + content + + if args.dry_run: + print(content) + else: + if os.geteuid() != 0: + print("写入 sources.list 需要 root 权限,请使用 sudo 运行或带 --dry-run 预览。", file=sys.stderr) + final_rc = final_rc or 1 + else: + # 确认 + if not args.yes: + print("将写入以下内容到:", args.path) + print("-" * 60) + print(content) + print("-" * 60) + ans = input("确认写入?[y/N]: ").strip().lower() + if ans not in ("y", "yes"): + print("已取消。") + else: + write_sources(content, args.path) + print(f"已写入 {args.path} 并备份原文件为 .bak-时间戳。") + if args.update: + rc = apt_update() + if rc == 0: + print("apt-get update 成功。") + else: + print(f"apt-get update 退出码 {rc},请检查网络或源配置。") + else: + write_sources(content, args.path) + print(f"已写入 {args.path} 并备份原文件为 .bak-时间戳。") + if args.update: + rc = apt_update() + if rc == 0: + print("apt-get update 成功。") + else: + print(f"apt-get update 退出码 {rc},请检查网络或源配置。") + + # pip 板块 + pip_key: str | None = args.pip_mirror + if pip_key is None: + # 若未提供且也未指定仅 APT,则展示 pip 板块交互 + pip_key = choose_pip_mirror_interactive() + if pip_key and pip_key != "skip": + rc_pip = run_pip_config(pip_key, args.dry_run, args.yes) + final_rc = rc_pip or final_rc + + sys.exit(final_rc) + + ok, reason = validate_supported(distro_id, version_id, codename) + if not ok: + print(reason, file=sys.stderr) + sys.exit(2) + + try: + content = render_sources(distro_id, codename, mirror) + except Exception as e: + print(str(e), file=sys.stderr) + sys.exit(3) + + header = ( + f"# Generated by changesource.py on {datetime.datetime.now().isoformat(timespec='seconds')}\n" + f"# distro={distro_id} version={version_id} codename={codename} arch={arch}\n" + f"# mirror={mirror}\n" + ) + content = header + content + + if args.dry_run: + print(content) + return + + if os.geteuid() != 0: + print("写入 sources.list 需要 root 权限,请使用 sudo 运行或带 --dry-run 预览。", file=sys.stderr) + sys.exit(1) + + # 确认 + if not args.yes: + print("将写入以下内容到:", args.path) + print("-" * 60) + print(content) + print("-" * 60) + ans = input("确认写入?[y/N]: ").strip().lower() + if ans not in ("y", "yes"): + print("已取消。") + return + + write_sources(content, args.path) + print(f"已写入 {args.path} 并备份原文件为 .bak-时间戳。") + + if args.update: + rc = apt_update() + if rc == 0: + print("apt-get update 成功。") + else: + print(f"apt-get update 退出码 {rc},请检查网络或源配置。") + + +if __name__ == "__main__": + main() + diff --git a/Linux脚本/systeminfo.py b/Linux脚本/systeminfo.py new file mode 100644 index 0000000..bf6492f --- /dev/null +++ b/Linux脚本/systeminfo.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +systeminfo.py - 将你给的 Bash 信息展示脚本等效地实现为 Python 版本。 +依赖:仅使用系统命令(ip、df、free、uptime、last、df 等),在没有命令时会尽量降级显示。 + +此脚本会收集并以彩色终端输出显示系统信息,包括: + - 系统概览(主机名、操作系统、内核、运行时间、负载) + - 硬件资源(CPU 型号与核数、CPU 温度、内存、磁盘分区) + - 网络连接(活动接口、IPv4/IPv6、MAC) + - 活动与统计(最近登录、已安装软件包数量、当前时间) + +实现细节:尽量使用标准系统工具获取信息;当某些工具不可用时,会尝试替代命令或回退为默认值。 +""" + +import subprocess +import shutil +import os +import re +from datetime import datetime +from typing import List + +# -------------------- 颜色 -------------------- +NONE = '\033[00m' +BOLD = '\033[1m' +TITLE_COLOR = '\033[01;36m' # 亮青色 +INFO_COLOR = '\033[01;33m' # 亮黄色 +VALUE_COLOR = '\033[00;37m' # 白色 +LABEL_COLOR = '\033[00;32m' # 绿色 +SUBTLE_COLOR = '\033[01;30m' # 深灰色 +BLUE_COLOR = '\033[01;34m' # 亮蓝色 +ORANGE_COLOR = '\033[01;33m' # 同 INFO_COLOR,用于 16 色终端 + +# -------------------- 运行本地系统命令 -------------------- +def run_cmd(cmd: str) -> str: + """运行一个 shell 命令并返回其标准输出(字符串)。错误时返回空字符串。""" + try: + out = subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL, text=True) + return out.strip() + except Exception: + return "" + +def has_cmd(name: str) -> bool: + """检查命令是否存在于 PATH 中。""" + return shutil.which(name) is not None + +# -------------------- ASCII 艺术头部 -------------------- +def print_header() -> None: + """打印带颜色的 ASCII 艺术化标题和分隔线。""" + print(f"{SUBTLE_COLOR}─────────────────────────────────────────────────────────────{NONE}") + print(f"{ORANGE_COLOR} _____ _ _ {NONE}") + print(f"{ORANGE_COLOR} | __ \\ | | (_) {NONE}") + print(f"{ORANGE_COLOR} | | | | ___ | |__ _ __ _ _ __ {NONE}") + print(f"{ORANGE_COLOR} | | | | / _ \\| '_ \\ | | / _` || '_ \\ {NONE}") + print(f"{ORANGE_COLOR} | |__| || __/| |_) || || (_| || | | |{NONE}") + print(f"{ORANGE_COLOR} |_____/ \\___||_.__/ |_| \\__,_||_| |_|{NONE}") + print() + print(f"{SUBTLE_COLOR}─────────────────────────────────────────────────────────────{NONE}") + +# -------------------- 系统概览 -------------------- +def system_overview() -> None: + """收集并打印系统概览信息。""" + print(f"{TITLE_COLOR}{BOLD}系统概览{NONE}") + hostname = run_cmd("hostname") or "N/A" + os_info = run_cmd("lsb_release -ds") or run_cmd("cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2 | tr -d '\"'") or "N/A" + kernel = run_cmd("uname -sr") or "N/A" + uptime = run_cmd("uptime -p").replace("up ", "") if has_cmd("uptime") else "N/A" + loadavg = run_cmd("uptime | awk -F'load average: ' '{print $2}'") or "N/A" + + print(f" {LABEL_COLOR}系统名称:{NONE} {VALUE_COLOR}{hostname}{NONE}") + print(f" {LABEL_COLOR}操作系统:{NONE} {VALUE_COLOR}{os_info}{NONE}") + print(f" {LABEL_COLOR}内核版本:{NONE} {VALUE_COLOR}{kernel}{NONE}") + print(f" {LABEL_COLOR}运行时间:{NONE} {VALUE_COLOR}{uptime}{NONE}") + print(f" {LABEL_COLOR}平均负载:{NONE} {VALUE_COLOR}{loadavg}{NONE}") + print(f"{SUBTLE_COLOR}─────────────────────────────────────────────────────────────{NONE}") + +# -------------------- 硬件资源 -------------------- +def hardware_resources() -> None: + """收集并打印硬件资源信息(CPU、内存、存储等)。""" + print(f"{TITLE_COLOR}{BOLD}硬件资源{NONE}") + # CPU 核心数与型号 + cpu_model = "N/A" + cores: int | str = "N/A" + if os.path.exists("/proc/cpuinfo"): + try: + with open("/proc/cpuinfo", "r", encoding="utf-8", errors="ignore") as f: + data = f.read() + m = re.search(r"model name\s+:\s+(.+)", data) + if m: + cpu_model = m.group(1).strip() + cores = len(re.findall(r"^processor\s+:", data, flags=re.M)) + except Exception: + pass + else: + cpu_model = run_cmd("uname -p") or cpu_model + cores = run_cmd("nproc") or cores + + # CPU 温度(可选,通过 sensors 命令) + cpu_temp = "" + if has_cmd("sensors"): + sensors_out = run_cmd("sensors") + m = re.search(r"(\+\d+\.\d+°C)", sensors_out) + if m: + cpu_temp = f"({INFO_COLOR}{m.group(1)}{VALUE_COLOR})" + + # 内存 + mem_total = run_cmd("free -g | awk 'NR==2{print $2\"GB\"}'") or "N/A" + mem_used_pct = run_cmd("free -m | awk 'NR==2{printf \"%.1f%%\", $3*100/$2 }'") or "N/A" + mem_avail = run_cmd("free -m | awk 'NR==2{print $7\"MB\"}'") or "N/A" + + print(f" {LABEL_COLOR}中央处理器 (CPU):{NONE}") + cores_str = str(cores) + print(f" {VALUE_COLOR}{cpu_model} - {cores_str} 核心 {cpu_temp}{NONE}") + print(f" {LABEL_COLOR}内存 (RAM):{NONE}") + print(f" {VALUE_COLOR}总计: {INFO_COLOR}{mem_total}{VALUE_COLOR} | 已用: {INFO_COLOR}{mem_used_pct}{VALUE_COLOR} | 可用: {INFO_COLOR}{mem_avail}{NONE}") + + # 存储 + print(f" {LABEL_COLOR}存储空间:{NONE}") + if has_cmd("df"): + df_cmd = "df -hT -x tmpfs -x devtmpfs -x devpts -x proc -x sysfs -x cgroup -x fusectl -x securityfs -x pstore -x efivarfs -x autofs 2>/dev/null | awk 'NR>1 {print $1, $2, $3, $4, $6, $7}'" + df_out = run_cmd(df_cmd) + if df_out: + for line in df_out.splitlines(): + parts = line.split(None, 5) + if len(parts) == 6: + device, ftype, size, used, percent, mount = parts + print(f" {INFO_COLOR}{mount}{NONE} ({VALUE_COLOR}{device} - {ftype}{NONE})") + print(f" {LABEL_COLOR}总:{NONE} {BLUE_COLOR}{size}{NONE}, {LABEL_COLOR}已用:{NONE} {BLUE_COLOR}{used}{NONE} ({VALUE_COLOR}{percent}{NONE})") + else: + print(f" {VALUE_COLOR}无法读取磁盘信息{NONE}") + else: + print(f" {VALUE_COLOR}df 命令不可用{NONE}") + + print(f"{SUBTLE_COLOR}─────────────────────────────────────────────────────────────{NONE}") + +# -------------------- 网络相关信息 -------------------- +def network_info() -> None: + """收集并打印网络接口与 IP 信息。""" + print(f"{TITLE_COLOR}{BOLD}网络连接{NONE}") + interfaces: List[str] = [] + ip_out = run_cmd("ip -o link show up") + if ip_out: + for line in ip_out.splitlines(): + m = re.match(r"\d+:\s+([^:@]+)", line) + if m: + interfaces.append(m.group(1)) + else: + try: + interfaces = [i for i in os.listdir("/sys/class/net")] + except Exception: + interfaces = [] + + has_ip = False + for iface in interfaces: + if iface == "lo": + continue + ip_addrs = run_cmd(f"ip -4 addr show dev {iface} 2>/dev/null | grep -oP 'inet \\K[\\d.]+' | tr '\\n' ' '").strip() + ip6 = run_cmd(f"ip -6 addr show dev {iface} 2>/dev/null | grep -oP 'inet6 \\K[0-9a-fA-F:]+' | grep -ivE '^fe80::' | head -n1 | tr '\\n' ' '").strip() + mac = run_cmd(f"ip link show dev {iface} 2>/dev/null | awk '/ether/ {{print $2}}'").strip() + if ip_addrs or ip6: + has_ip = True + iface_label = "" + if re.match(r'^(wlan|wlp|ath|ra)', iface): + iface_label = f"({LABEL_COLOR}WiFi{NONE}) " + elif re.match(r'^(eth|enp|eno)', iface): + iface_label = f"({LABEL_COLOR}有线{NONE}) " + elif re.match(r'^(docker|br-|veth|tun|tap|virbr)', iface): + iface_label = f"({LABEL_COLOR}虚拟{NONE}) " + else: + br = run_cmd(f"ip link show {iface} | grep -i bridge || true") + if br: + iface_label = f"({LABEL_COLOR}桥接{NONE}) " + + print(f" {INFO_COLOR}{BOLD}{iface}{NONE} {iface_label}") + if ip_addrs: + print(f" {LABEL_COLOR}IPv4:{NONE} {VALUE_COLOR}{ip_addrs.strip()}{NONE}") + if ip6: + print(f" {LABEL_COLOR}IPv6:{NONE} {VALUE_COLOR}{ip6.strip()}{NONE}") + if mac: + print(f" {LABEL_COLOR}MAC:{NONE} {VALUE_COLOR}{mac}{NONE}") + + if not has_ip: + print(f" {VALUE_COLOR}未检测到活动的网络连接或IP地址{NONE}") + print(f"{SUBTLE_COLOR}─────────────────────────────────────────────────────────────{NONE}") + +# -------------------- 活动与统计 -------------------- +def activity_stats() -> None: + """收集并打印用户活动与系统统计信息(最近登录、包数量等)。""" + print(f"{TITLE_COLOR}{BOLD}活动与统计{NONE}") + last_login_info = run_cmd("last -n 1 -wFai 2>/dev/null | head -n 1") + last_login_display = "" + if not last_login_info or "wtmp begins" in last_login_info or len(last_login_info.split()) < 5: + last_login_display = f"{VALUE_COLOR}无先前登录记录或记录格式异常{NONE}" + else: + parts = last_login_info.split() + user = parts[0] if len(parts) > 0 else "N/A" + from_host = parts[2] if len(parts) > 2 else "N/A" + time_fields = " ".join(parts[3:7]) if len(parts) >= 7 else " ".join(parts[3:]) + try: + formatted = datetime.strptime(time_fields + " " + str(datetime.now().year), "%b %d %H:%M %Y").strftime("%Y-%m-%d %H:%M:%S") + except Exception: + formatted = f"{time_fields} (raw)" + last_login_display = f"{LABEL_COLOR}用户:{NONE} {INFO_COLOR}{user}{NONE}, {LABEL_COLOR}来自:{NONE} {INFO_COLOR}{from_host}{NONE}, {LABEL_COLOR}时间:{NONE} {VALUE_COLOR}{formatted}{NONE}" + + package_count = "N/A" + package_label = "" + if has_cmd("dpkg-query"): + package_count = run_cmd("dpkg-query -f '${Package}\\n' -W 2>/dev/null | wc -l") or "N/A" + package_label = "(Debian/APT)" + elif has_cmd("rpm"): + package_count = run_cmd("rpm -qa 2>/dev/null | wc -l") or "N/A" + package_label = "(RPM/Yum/Dnf)" + elif has_cmd("pacman"): + package_count = run_cmd("pacman -Qq 2>/dev/null | wc -l") or "N/A" + package_label = "(Pacman)" + else: + package_label = "(未知包管理器)" + + current_dt = datetime.now().strftime("%Y年%m月%d日 %A %H:%M:%S") + print(f" {LABEL_COLOR}上次登录:{NONE} {last_login_display}") + print(f" {LABEL_COLOR}软件包数:{NONE} {VALUE_COLOR}{package_count} {package_label}{NONE}") + print(f" {LABEL_COLOR}当前登录时间:{NONE} {VALUE_COLOR}{current_dt}{NONE}") + print(f"{SUBTLE_COLOR}─────────────────────────────────────────────────────────────{NONE}") + +# -------------------- 主程序 -------------------- +def main() -> None: + print_header() + system_overview() + hardware_resources() + network_info() + activity_stats() + +if __name__ == "__main__": + main() diff --git a/换源面板 b/换源面板 new file mode 100644 index 0000000..5e1dff9 --- /dev/null +++ b/换源面板 @@ -0,0 +1,13 @@ +============请选择需要换源的命令============ +Linux发行版软件源: +1.Ubuntu(支持22 24 amd64 arm64 官方源 清华源 阿里源) +2.Debian(支持11 12 amd64 arm64 官方源 清华源 阿里源) +3.Termux(支持清华源) + +Python的pip镜像源: +4.清华源 +5.阿里源 +6.豆瓣源 +7.腾讯源 +8.官方源 +=========================================== \ No newline at end of file