首页
统计信息
友情链接
壁纸
Search
1
【更新】CommentToMail typecho2017&v4.1& Mailer三版本,支持php5.6/7,插件详解
158,346 阅读
2
【90APT开源免费】第三代哈弗H6、哈弗大狗、H6经典版、坦克300华阳安波福车机开启无线ADB、升级地图、安装软件全流程
39,254 阅读
3
CentOS 7安装bbr教程
13,036 阅读
4
深信服超融合架构测试介绍
12,686 阅读
5
纯小白10分钟变身linux建站高手?宝塔linux面板全体验
12,621 阅读
技术相关
ACG相关
胡言乱语
数码杂烩
登录
Search
标签搜索
进击的巨人
漫画
宝塔
php
typecho
diy
vps
折腾
动漫
优酷路由宝
ubuntu
路由器
QQ
KMS
王忘杰
累计撰写
311
篇文章
累计收到
179
条评论
首页
栏目
技术相关
ACG相关
胡言乱语
数码杂烩
页面
统计信息
友情链接
壁纸
搜索到
279
篇与
的结果
2026-01-06
图形界面升级 Oceanbase数据库4.3.5.2升级至4.5 OCP云平台4.3.5升级至4.4
Oceanbase数据库4.3.5.2升级至4.5https://www.oceanbase.com/docs/common-ocp-1000000004276440下载升级所需软件包,使用OCP平台进入目标集群,选择升级版本,根据提示补全软件包升级成功按官方要求,集群升级完成后,请务必对集群手动发起一次全量备份。OCP云平台4.3.5升级至4.4https://www.oceanbase.com/docs/common-ocp-1000000004276672在王工升级时,文档存在错误,4.4依赖的java版本是java17,已向官方反馈下载并解压升级包tar xvzf ocp-all-in-one-4.4.0-20251114143405.el7.x86_64.tar.gz启动obd,运行升级向导cd ocp-all-in-one/bin && bash install.sh source ~/.oceanbase-all-in-one/bin/env.sh obd web upgrade访问网页如忘记密码,其历史记录位于ocp服务器,如果是root用户安装则为cat /root/.obd/cluster/ocp集群名称/config.yaml升级中会出现报错,此时需要卸载java1.8并安装openjdk17,并点击重试,即可升级成功yum remove jre yum install java-17-openjdk.x86_64
2026年01月06日
11 阅读
0 评论
0 点赞
2025-12-31
windows PC 服务器使用技巧、经验记录
1、关闭“幽灵”和“熔断”漏洞防护可恢复部分性能,有风险:https://www.grc.com/inspectre.htm2、Chrome开启多线程下载在地址栏输入 chrome://flags/,然后在搜索框中输入 Parallel downloading,选择enabled,重启Chrome3、如何在 Windows 上查看 HEIC 格式照片?https://www.microsoft.com/en-us/p/heif-image-extensions/9pmmsr1cgpwg?activetab=pivot:overviewtab4、win10自动登陆账号打开“设置-账户-登录选项”;取消“需要通过Windows Hello登录Microsoft账户”前面的复选框;点击桌面左下角的搜索栏,输入“netplwiz”,然后右侧点击“以管理员身份运行”;取消“要使用本计算机,用户必须输入用户名和密码”前面的复选框,并根据提示输入正确的账号名和密码;5、关闭休眠文件使用管理员运行cmd,执行:powercfg -h off 6、win10企业版无法访问共享文件夹首先我们按“window R键”打开运行窗口。在该窗口文本输入处输入“gpedit.msc”,这样本地组策略编辑器就能被启动。进入编辑器页面后,找到并点击页面左侧列的“计算机配置”。4.接着点击计算机配置下的“管理模板”选项。然后在管理模板下,依次点开“网络”-“Lanman工作站”。打开“Lanman工作站”后,在页面右侧列找到“启用不安全的来宾登录”。7.双击该选项,并选择“已启用”,再点击“确定”即可。启动不安全的来宾登录7、WIN10 添加自动启动程序无效的解决方案将启动项目加入Hkey_local_machine\software\wow6432node\microsoft\windows\currentversion\run或放入开机启动目录 运行-shell:Common Startup8、WPS右键无新建doc、xls等快捷方式重置插件,重新注册9、使加域的计算机具备本地管理员权限使用本地管理员登录计算机,在本地管理员组中加入域用户10、win11右键管理员菜单样式j经典 reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve x新版 reg delete "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}" /f11、命令行禁用UACC:\Windows\System32\cmd.exe /k %windir%\System32\reg.exe ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 0 /f 0为禁用 1为默认12、win11任务栏展开https://github.com/valinet/ExplorerPatcher/releases13、windows查看wifi无线信号强度netsh wlan show interfaces14、解决FTP因windows防火墙拦截的方法防火墙放行 windows-system32-svchost.exe(windows服务主进程)15、windows远程桌面登录提示内部错误Error Code 0x4微软论坛搜索https://social.technet.microsoft.com/Forums/en-US/f7ed9049-36a5-4def-90f8-14ebb70e6671/error-code-0x4新建注册表项Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server MaxOutstandingConnections DWORD 300016、一键修改远程桌面端口、开放防火墙、开启ping回显REG QUERY "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Remote Assistance" /v "fAllowToGetHelp" reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\Wds\rdpwd\Tds\tcp" /v "PortNumber" /t REG_DWORD /d "0xf3b6" /f reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v "PortNumber" /t REG_DWORD /d "0xf3b6" /f net stop TermService /y net start TermService /y netsh advfirewall firewall add rule name="secrdp" dir=in action=allow protocol=TCP localport=62390 netsh advfirewall firewall add rule name="_Ping" dir=in protocol=icmpv4 action=allow17、配置windows计划任务运行 任务计划程序常规-不管用户是否登录等要运行常规-使用最高权限运行每天每2分钟运行一次触发器操作-启动程序设置-如果此任务已经运行,以下规则适用:停止现有实例18、windows重置SIDC:\Windows\System32\Sysprep勾选通用,等待重启即可19、.NET:这台计算机中已经安装了.net framework 4.8或更高的更新安装.NET Framework 4.8 开发者工具包https://dotnet.microsoft.com/zh-cn/download/visual-studio-sdks20、windows计算md5certutil -hashfile 文件 MD521、教你一个命令,几十秒钟给ltsc安装微软商店Storehttps://bbs.pcbeta.com/viewthread-1945054-1-1.html以管理员身份运行cmd执行wsreset -i
2025年12月31日
727 阅读
0 评论
5 点赞
2025-12-31
anolis9 alma9 Linux docker部署和镜像源配置
1、安装dockeryum install -y yum-utils #官方源 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo #阿里云加速源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum install docker-ce docker-ce-cli containerd.io2、配置docker网段和加速源/etc/docker/daemon.json { "bip": "192.168.120.1/24", "registry-mirrors": [ "https://docker.1ms.run" ] }3、启动服务systemctl enable --now docker4、镜像源推荐毫秒镜像https://1ms.run/Docker CE 软件仓库 清华大学https://mirrors.tuna.tsinghua.edu.cn/help/docker-ce/
2025年12月31日
15 阅读
0 评论
0 点赞
2025-12-30
自建harbor docker镜像库,将docker镜像迁移至K8S Kubernetes集群运行
环境准备服务器1:安装docker环境,安装harbor服务器2:安装docker环境,添加docker仓库测试服务器集群1:运行K8S集群部署harbor docker镜像库服务器1下载解压 https://github.com/goharbor/harbor/releasestar -xvzf harbor-online-installer-v2.14.1.tgz进入目录cd harbor创建配置文件cp harbor.yml.tmpl harbor.yml准备真实有效的证书,并通过DNS服务器正确解析域名reg.90apt.com至192.168.4.9修改配置文件harbor.ymlhostname: reg.90apt.com # http related config http: # port for http, default is 80. If https enabled, this port will redirect to https port port: 80 https: # https port for harbor, default is 443 port: 443 # The path of cert and key files for nginx certificate: /root/90apt.pem private_key: /root/90apt.key 预配置./prepare部署./install.sh查看状态docker compose ps关闭docker compose stop启动docker compose start后台运行docker compose up -d删除harbor docker-compose down默认账号密码admin Harbor12345docker主机添加Harbor仓库服务器2添加并重启docker/etc/docker/daemon.json { "registry-mirrors": [ "https://docker.1ms.run" ], "insecure-registries" : ["reg.90apt.com"] } systemctl restart docker查看docker配置是否成功docker info登录harbordocker login reg.90apt.com运行一个uptime-kumadocker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1修改镜像tagdocker tag louislam/uptime-kuma:1 reg.90apt.com/library/uptime-kuma:1推送docker push reg.90apt.com/library/uptime-kuma:1拉取镜像docker pull reg.90apt.com/library/uptime-kuma:1K8S使用自建镜像仓库服务器集群1K8S官方教程,使用命令行方式https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/pull-image-private-registry/使用命令行创建Secretkubectl create secret docker-registry 90apt \ --docker-server=reg.90apt.com \ --docker-username=admin \ --docker-password=Harbor12345 \ --docker-email=<你的邮箱地址,此项可忽略>检查 Secret 90aptkubectl get secret 90apt --output=yaml创建一个使用Secret的Podmy-private-reg-pod.yamlapiVersion: v1 kind: Pod metadata: name: private-reg labels: app: private-reg spec: containers: - name: private-reg-container image: reg.90apt.com/library/uptime-kuma:1 ports: - containerPort: 3001 protocol: TCP imagePullSecrets: - name: 90apt创建一个service90aptservice.yamlapiVersion: v1 kind: Service metadata: name: uptime-kuma-service spec: type: NodePort selector: app: private-reg ports: - port: 3001 # Service内部端口 targetPort: 3001 # Pod容器端口 nodePort: 30010 # 节点上的端口(必须在30000-32767范围内)运行并查看kubectl apply -f my-private-reg-pod.yaml kubectl apply -f 90aptservice.yaml查看运行状态kubectl get pod private-reg NAME READY STATUS RESTARTS AGE private-reg 1/1 Running 0 174m 查看serveices端口映射情况kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) uptime-kuma-service NodePort 10.109.66.144 <none> 3001:30010/TCP 173m 查看详情kubectl describe pod private-reg Name: private-reg Namespace: default Priority: 0 Node: testk8s-node2/192.168.4.12 Start Time: Wed, 31 Dec 2025 12:25:51 +0800 .......... Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s node.kubernetes.io/unreachable:NoExecute op=Exists for 300s Events: <none> 通过K8S集群访问测试,成功http://node-ip:30010
2025年12月30日
26 阅读
0 评论
0 点赞
2025-12-27
Xiaomi Redmi 红米 Note 7 Pro 输入类原生系统LineageOS 23
https://wiki.lineageos.org/devices/violet/https://wiki.lineageos.org/devices/#xiaomi官网教程详细,输入即可刷入后,卡顿减轻,支持4K60fps录像,体验较miui好一些
2025年12月27日
32 阅读
0 评论
0 点赞
2025-12-18
AI开发SMB文件复制工具 开机自启 后台运行 定时任务 密码加密
import tkinter as tk from tkinter import ttk, messagebox, filedialog, scrolledtext import os import threading import schedule import time from datetime import datetime, date, timedelta from smb.SMBConnection import SMBConnection import socket import json import sys import io import base64 from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC import pystray from PIL import Image, ImageDraw import winreg import atexit class SMBCopierApp: def __init__(self, root): self.root = root self.root.title("SMB文件复制工具") self.root.geometry("850x750") self.root.minsize(800, 600) # 系统托盘图标 self.tray_icon = None self.in_background = False # 存储定时任务 self.scheduled_tasks = [] self.schedule_thread = None self.running = False # 上一次执行时间,避免重复执行 self.last_execution_time = None self.last_execution_day = None # 配置文件路径 self.config_file = "smb_copier_config.json" # 密钥文件路径 self.key_file = "smb_copier.key" # 加密密钥(从文件加载或生成新的) self.encryption_key = self.load_or_create_key() # 创建主容器 self.main_container = ttk.PanedWindow(root, orient=tk.VERTICAL) self.main_container.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 创建上部分(设置面板) self.settings_frame = ttk.Frame(self.main_container) self.main_container.add(self.settings_frame, weight=1) # 创建下部分(日志面板) self.log_frame = ttk.Frame(self.main_container) self.main_container.add(self.log_frame, weight=1) self.setup_settings_ui() self.setup_log_ui() self.load_config() self.start_schedule_thread() # 设置关闭事件 self.root.protocol("WM_DELETE_WINDOW", self.on_closing) # 注册退出清理 atexit.register(self.cleanup) def generate_key(self): """生成一个新的加密密钥""" try: # 使用用户密码生成密钥 password = "smb_copier_default_password".encode() # 你可以修改这个默认密码 salt = b'smb_copier_salt' # 盐值,用于密钥派生 # 使用PBKDF2派生密钥 kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, ) key = base64.urlsafe_b64encode(kdf.derive(password)) # 保存密钥到文件 with open(self.key_file, 'wb') as f: f.write(key) self.log("已生成新的加密密钥") return key except Exception as e: self.log(f"生成密钥失败: {str(e)}") # 如果失败,使用一个简单的备用密钥 return base64.urlsafe_b64encode(b'smb-copier-default-key-12345678') def load_or_create_key(self): """加载或创建加密密钥""" try: if os.path.exists(self.key_file): with open(self.key_file, 'rb') as f: key = f.read() self.log("已加载现有的加密密钥") return key else: return self.generate_key() except Exception as e: self.log(f"加载密钥失败: {str(e)},使用备用密钥") # 使用一个简单的备用密钥 return base64.urlsafe_b64encode(b'smb-copier-default-key-12345678') def encrypt_password(self, password): """加密密码""" if not password: return "" try: fernet = Fernet(self.encryption_key) encrypted = fernet.encrypt(password.encode()) return encrypted.decode('utf-8') except Exception as e: self.log(f"密码加密失败: {str(e)}") return password # 如果加密失败,返回原始密码 def decrypt_password(self, encrypted_password): """解密密码""" if not encrypted_password: return "" try: fernet = Fernet(self.encryption_key) decrypted = fernet.decrypt(encrypted_password.encode()) return decrypted.decode('utf-8') except Exception as e: # 如果解密失败,可能是旧版本的未加密密码 self.log(f"密码解密失败,可能为未加密密码: {str(e)}") return encrypted_password # 返回原始值(可能是未加密的密码) def setup_settings_ui(self): """设置上部分的界面""" # 创建滚动框架 self.settings_canvas = tk.Canvas(self.settings_frame, highlightthickness=0) self.settings_scrollbar = ttk.Scrollbar(self.settings_frame, orient=tk.VERTICAL, command=self.settings_canvas.yview) self.settings_inner_frame = ttk.Frame(self.settings_canvas) # 配置滚动 self.settings_canvas.configure(yscrollcommand=self.settings_scrollbar.set) self.settings_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.settings_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # 将内部框架添加到画布 self.canvas_window = self.settings_canvas.create_window((0, 0), window=self.settings_inner_frame, anchor=tk.NW) # 绑定事件 self.settings_inner_frame.bind('<Configure>', self.on_settings_frame_configure) self.settings_canvas.bind('<Configure>', self.on_settings_canvas_configure) # 使用Notebook选项卡组织内容 self.notebook = ttk.Notebook(self.settings_inner_frame) self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 创建各个选项卡 self.setup_source_tab() self.setup_target_tab() self.setup_copy_tab() self.setup_schedule_tab() def on_settings_frame_configure(self, event=None): """内部框架配置时调整画布滚动区域""" self.settings_canvas.configure(scrollregion=self.settings_canvas.bbox('all')) def on_settings_canvas_configure(self, event): """画布大小变化时调整内部框架宽度""" self.settings_canvas.itemconfig(self.canvas_window, width=event.width) def setup_log_ui(self): """设置下部分的日志界面""" # 日志标题和清除按钮 log_header = ttk.Frame(self.log_frame) log_header.pack(fill=tk.X, padx=5, pady=(5, 0)) ttk.Label(log_header, text="操作日志", font=('Arial', 10, 'bold')).pack(side=tk.LEFT) # 添加密钥管理按钮 ttk.Button(log_header, text="重新生成密钥", command=self.regenerate_key, width=12).pack(side=tk.RIGHT, padx=(0, 5)) ttk.Button(log_header, text="清除日志", command=self.clear_log, width=10).pack(side=tk.RIGHT) # 日志文本框 self.log_text = scrolledtext.ScrolledText( self.log_frame, wrap=tk.WORD, height=12, font=('Consolas', 9) ) self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 添加初始日志 self.log("SMB文件复制工具已启动") self.log(f"当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") def regenerate_key(self): """重新生成加密密钥""" if messagebox.askyesno("确认", "重新生成密钥将导致已保存的加密密码无法解密,确定要继续吗?"): try: # 删除旧的密钥文件 if os.path.exists(self.key_file): os.remove(self.key_file) self.log("已删除旧的密钥文件") # 生成新的密钥 self.encryption_key = self.generate_key() # 清除配置文件中的加密密码 config = {} if os.path.exists(self.config_file): with open(self.config_file, 'r', encoding='utf-8') as f: config = json.load(f) # 清除加密的密码字段 password_fields = ['source_smb_password', 'target_smb_password'] for field in password_fields: if field in config: config[field] = "" # 保存配置 with open(self.config_file, 'w', encoding='utf-8') as f: json.dump(config, f, ensure_ascii=False, indent=2) # 清除界面中的密码 self.source_smb_password.set("") self.target_smb_password.set("") self.log("已重新生成加密密钥,请重新输入并保存密码") messagebox.showinfo("成功", "加密密钥已重新生成,请重新输入并保存密码。") except Exception as e: self.log(f"重新生成密钥失败: {str(e)}") messagebox.showerror("错误", f"重新生成密钥失败: {str(e)}") def setup_source_tab(self): """设置源目录选项卡""" self.source_tab = ttk.Frame(self.notebook) self.notebook.add(self.source_tab, text="源目录") # 源目录类型选择 self.source_type = tk.StringVar(value="local") type_frame = ttk.LabelFrame(self.source_tab, text="目录类型", padding="10") type_frame.pack(fill=tk.X, padx=10, pady=(10, 5)) ttk.Radiobutton(type_frame, text="本地目录", variable=self.source_type, value="local", command=self.toggle_source_type).pack(side=tk.LEFT, padx=(0, 20)) ttk.Radiobutton(type_frame, text="SMB目录", variable=self.source_type, value="smb", command=self.toggle_source_type).pack(side=tk.LEFT) # 本地源目录 self.source_local_frame = ttk.LabelFrame(self.source_tab, text="本地目录设置", padding="10") self.source_local_frame.pack(fill=tk.X, padx=10, pady=5) ttk.Label(self.source_local_frame, text="目录路径:").grid(row=0, column=0, sticky=tk.W, pady=(0, 5)) self.source_local_path = tk.StringVar() source_entry = ttk.Entry(self.source_local_frame, textvariable=self.source_local_path) source_entry.grid(row=0, column=1, sticky=tk.EW, padx=(5, 5), pady=(0, 5)) ttk.Button(self.source_local_frame, text="浏览", command=self.browse_source_local, width=8).grid(row=0, column=2, pady=(0, 5)) # 配置网格权重 self.source_local_frame.columnconfigure(1, weight=1) # SMB源目录设置 self.source_smb_frame = ttk.LabelFrame(self.source_tab, text="SMB目录设置", padding="10") self.source_smb_frame.pack(fill=tk.X, padx=10, pady=5) # SMB路径 ttk.Label(self.source_smb_frame, text="SMB路径:").grid(row=0, column=0, sticky=tk.W, pady=(0, 5)) self.source_smb_path = tk.StringVar() smb_entry = ttk.Entry(self.source_smb_frame, textvariable=self.source_smb_path) smb_entry.grid(row=0, column=1, sticky=tk.EW, padx=(5, 5), pady=(0, 5)) # 用户名和密码 ttk.Label(self.source_smb_frame, text="用户名:").grid(row=1, column=0, sticky=tk.W, pady=(0, 5)) self.source_smb_username = tk.StringVar() ttk.Entry(self.source_smb_frame, textvariable=self.source_smb_username, width=20).grid(row=1, column=1, sticky=tk.W, padx=(5, 5), pady=(0, 5)) ttk.Label(self.source_smb_frame, text="密码:").grid(row=1, column=2, sticky=tk.W, pady=(0, 5)) self.source_smb_password = tk.StringVar() ttk.Entry(self.source_smb_frame, textvariable=self.source_smb_password, show="*", width=20).grid(row=1, column=3, sticky=tk.W, padx=(5, 0), pady=(0, 5)) # 显示密码复选框 self.show_source_password = tk.BooleanVar(value=False) ttk.Checkbutton(self.source_smb_frame, text="显示密码", variable=self.show_source_password, command=self.toggle_source_password_visibility).grid(row=2, column=3, sticky=tk.W, pady=(0, 5)) # 测试连接按钮 ttk.Button(self.source_smb_frame, text="测试连接", command=self.test_source_smb_connection).grid(row=2, column=0, columnspan=2, sticky=tk.W, pady=(0, 5)) # 示例标签 example_frame = ttk.Frame(self.source_smb_frame) example_frame.grid(row=3, column=0, columnspan=4, sticky=tk.W, pady=(5, 0)) ttk.Label(example_frame, text="示例格式: \\\\192.168.1.1\\share\\目录", foreground="gray", font=('Arial', 8)).pack(side=tk.LEFT) # 配置网格权重 self.source_smb_frame.columnconfigure(1, weight=1) # 初始化UI状态 self.toggle_source_type() def toggle_source_password_visibility(self): """切换源密码显示状态""" if self.show_source_password.get(): self.source_smb_frame.winfo_children()[6].config(show="") # 密码输入框 else: self.source_smb_frame.winfo_children()[6].config(show="*") def toggle_target_password_visibility(self): """切换目标密码显示状态""" if self.show_target_password.get(): self.target_smb_frame.winfo_children()[6].config(show="") # 密码输入框 else: self.target_smb_frame.winfo_children()[6].config(show="*") def setup_target_tab(self): """设置目标目录选项卡""" self.target_tab = ttk.Frame(self.notebook) self.notebook.add(self.target_tab, text="目标目录") # 目标目录类型选择 self.target_type = tk.StringVar(value="local") type_frame = ttk.LabelFrame(self.target_tab, text="目录类型", padding="10") type_frame.pack(fill=tk.X, padx=10, pady=(10, 5)) ttk.Radiobutton(type_frame, text="本地目录", variable=self.target_type, value="local", command=self.toggle_target_type).pack(side=tk.LEFT, padx=(0, 20)) ttk.Radiobutton(type_frame, text="SMB目录", variable=self.target_type, value="smb", command=self.toggle_target_type).pack(side=tk.LEFT) # 本地目标目录 self.target_local_frame = ttk.LabelFrame(self.target_tab, text="本地目录设置", padding="10") self.target_local_frame.pack(fill=tk.X, padx=10, pady=5) ttk.Label(self.target_local_frame, text="目录路径:").grid(row=0, column=0, sticky=tk.W, pady=(0, 5)) self.target_local_path = tk.StringVar() target_entry = ttk.Entry(self.target_local_frame, textvariable=self.target_local_path) target_entry.grid(row=0, column=1, sticky=tk.EW, padx=(5, 5), pady=(0, 5)) ttk.Button(self.target_local_frame, text="浏览", command=self.browse_target_local, width=8).grid(row=0, column=2, pady=(0, 5)) # 配置网格权重 self.target_local_frame.columnconfigure(1, weight=1) # SMB目标目录设置 self.target_smb_frame = ttk.LabelFrame(self.target_tab, text="SMB目录设置", padding="10") self.target_smb_frame.pack(fill=tk.X, padx=10, pady=5) # SMB路径 ttk.Label(self.target_smb_frame, text="SMB路径:").grid(row=0, column=0, sticky=tk.W, pady=(0, 5)) self.target_smb_path = tk.StringVar() target_smb_entry = ttk.Entry(self.target_smb_frame, textvariable=self.target_smb_path) target_smb_entry.grid(row=0, column=1, sticky=tk.EW, padx=(5, 5), pady=(0, 5)) # 用户名和密码 ttk.Label(self.target_smb_frame, text="用户名:").grid(row=1, column=0, sticky=tk.W, pady=(0, 5)) self.target_smb_username = tk.StringVar() ttk.Entry(self.target_smb_frame, textvariable=self.target_smb_username, width=20).grid(row=1, column=1, sticky=tk.W, padx=(5, 5), pady=(0, 5)) ttk.Label(self.target_smb_frame, text="密码:").grid(row=1, column=2, sticky=tk.W, pady=(0, 5)) self.target_smb_password = tk.StringVar() ttk.Entry(self.target_smb_frame, textvariable=self.target_smb_password, show="*", width=20).grid(row=1, column=3, sticky=tk.W, padx=(5, 0), pady=(0, 5)) # 显示密码复选框 self.show_target_password = tk.BooleanVar(value=False) ttk.Checkbutton(self.target_smb_frame, text="显示密码", variable=self.show_target_password, command=self.toggle_target_password_visibility).grid(row=2, column=3, sticky=tk.W, pady=(0, 5)) # 测试连接按钮 ttk.Button(self.target_smb_frame, text="测试连接", command=self.test_target_smb_connection).grid(row=2, column=0, columnspan=2, sticky=tk.W, pady=(0, 5)) # 示例标签 example_frame = ttk.Frame(self.target_smb_frame) example_frame.grid(row=3, column=0, columnspan=4, sticky=tk.W, pady=(5, 0)) ttk.Label(example_frame, text="示例格式: \\\\192.168.1.1\\share\\目录", foreground="gray", font=('Arial', 8)).pack(side=tk.LEFT) # 配置网格权重 self.target_smb_frame.columnconfigure(1, weight=1) # 初始化UI状态 self.toggle_target_type() def toggle_source_type(self): """切换源目录类型""" if self.source_type.get() == "local": self.source_smb_frame.pack_forget() self.source_local_frame.pack(fill=tk.X, padx=10, pady=5) else: self.source_local_frame.pack_forget() self.source_smb_frame.pack(fill=tk.X, padx=10, pady=5) def toggle_target_type(self): """切换目标目录类型""" if self.target_type.get() == "local": self.target_smb_frame.pack_forget() self.target_local_frame.pack(fill=tk.X, padx=10, pady=5) else: self.target_local_frame.pack_forget() self.target_smb_frame.pack(fill=tk.X, padx=10, pady=5) def setup_copy_tab(self): """设置复制选项选项卡""" self.copy_tab = ttk.Frame(self.notebook) self.notebook.add(self.copy_tab, text="复制选项") # 复制模式 copy_frame = ttk.LabelFrame(self.copy_tab, text="复制模式", padding="15") copy_frame.pack(fill=tk.X, padx=10, pady=(10, 15)) self.copy_mode = tk.StringVar(value="overwrite") ttk.Radiobutton(copy_frame, text="覆盖 - 如果文件已存在则覆盖", variable=self.copy_mode, value="overwrite").pack(anchor=tk.W, pady=(0, 5)) ttk.Radiobutton(copy_frame, text="忽略 - 如果文件已存在则跳过", variable=self.copy_mode, value="skip").pack(anchor=tk.W) # 开机自启选项 auto_start_frame = ttk.LabelFrame(self.copy_tab, text="程序设置", padding="15") auto_start_frame.pack(fill=tk.X, padx=10, pady=(0, 15)) # 开机自启 self.auto_start = tk.BooleanVar(value=False) auto_start_check = ttk.Checkbutton(auto_start_frame, text="开机自启", variable=self.auto_start, command=self.toggle_auto_start) auto_start_check.pack(anchor=tk.W, pady=(0, 8)) # 后台运行 self.background_mode = tk.BooleanVar(value=False) background_check = ttk.Checkbutton(auto_start_frame, text="后台运行(最小化到系统托盘)", variable=self.background_mode, command=self.toggle_background_mode) background_check.pack(anchor=tk.W) # 立即复制按钮 btn_frame = ttk.Frame(self.copy_tab) btn_frame.pack(fill=tk.X, padx=10, pady=(0, 10)) ttk.Button(btn_frame, text="立即执行复制", command=self.execute_copy_now, style="Accent.TButton").pack(side=tk.LEFT, padx=(0, 10)) ttk.Button(btn_frame, text="保存配置", command=self.save_config).pack(side=tk.LEFT) def toggle_auto_start(self): """切换开机自启""" try: if self.auto_start.get(): # 设置开机自启 self.set_auto_start(True) self.log("已启用开机自启") else: # 取消开机自启 self.set_auto_start(False) self.log("已禁用开机自启") except Exception as e: self.log(f"设置开机自启失败: {str(e)}") messagebox.showerror("错误", f"设置开机自启失败: {str(e)}") def set_auto_start(self, enable=True): """设置开机自启""" try: # 获取当前可执行文件路径 if getattr(sys, 'frozen', False): # 如果是打包后的exe exe_path = sys.executable else: # 如果是Python脚本 exe_path = sys.executable script_path = os.path.abspath(__file__) exe_path = f'"{exe_path}" "{script_path}"' if sys.platform == "win32": # Windows系统 - 使用注册表 key = winreg.HKEY_CURRENT_USER subkey = r"Software\Microsoft\Windows\CurrentVersion\Run" try: with winreg.OpenKey(key, subkey, 0, winreg.KEY_SET_VALUE) as reg_key: if enable: winreg.SetValueEx(reg_key, "SMBCopier", 0, winreg.REG_SZ, exe_path) else: try: winreg.DeleteValue(reg_key, "SMBCopier") except: pass # 如果键不存在,忽略 except Exception as e: self.log(f"访问注册表失败: {str(e)}") else: # Linux/Mac系统 - 使用启动文件夹 startup_dir = "" if sys.platform == "darwin": # Mac startup_dir = os.path.expanduser("~/Library/LaunchAgents") else: # Linux startup_dir = os.path.expanduser("~/.config/autostart") if startup_dir: os.makedirs(startup_dir, exist_ok=True) desktop_file = os.path.join(startup_dir, "smb_copier.desktop") if enable: desktop_content = f"""[Desktop Entry] Type=Application Name=SMB Copier Exec={exe_path} Hidden=false X-GNOME-Autostart-enabled=true """ with open(desktop_file, "w") as f: f.write(desktop_content) else: if os.path.exists(desktop_file): os.remove(desktop_file) except Exception as e: raise Exception(f"设置开机自启失败: {str(e)}") def toggle_background_mode(self): """切换后台运行模式""" if self.background_mode.get(): self.log("已启用后台运行模式") if not self.tray_icon: self.create_tray_icon() else: self.log("已禁用后台运行模式") # 如果正在后台运行,则恢复窗口 if self.in_background: self.restore_from_tray() def create_tray_icon(self): """创建系统托盘图标""" try: # 创建图标 image = Image.new('RGB', (64, 64), color='white') draw = ImageDraw.Draw(image) # 绘制一个简单的图标 draw.rectangle([16, 16, 48, 48], outline='blue', width=3) draw.line([20, 20, 44, 44], fill='green', width=3) draw.line([20, 44, 44, 20], fill='red', width=3) # 创建菜单 menu = ( pystray.MenuItem("显示窗口", self.restore_from_tray), pystray.MenuItem("立即复制", self.execute_copy_now), pystray.MenuItem("退出", self.quit_from_tray) ) # 创建托盘图标 self.tray_icon = pystray.Icon("smb_copier", image, "SMB文件复制工具", menu) # 启动托盘图标线程 threading.Thread(target=self.tray_icon.run, daemon=True).start() self.log("系统托盘图标已创建") except Exception as e: self.log(f"创建系统托盘图标失败: {str(e)}") messagebox.showwarning("警告", f"创建系统托盘图标失败: {str(e)}\n请确保已安装PIL和pystray库。") def restore_from_tray(self): """从托盘恢复窗口""" if self.tray_icon: self.root.after(0, self._show_window) def _show_window(self): """显示窗口""" self.root.deiconify() # 显示窗口 self.root.lift() # 置于顶层 self.root.focus_force() # 获取焦点 self.in_background = False def quit_from_tray(self): """从托盘退出程序""" if self.tray_icon: self.tray_icon.stop() self.root.after(0, self.root.quit) def minimize_to_tray(self): """最小化到系统托盘""" if self.tray_icon: self.root.withdraw() # 隐藏窗口 self.in_background = True self.log("程序已最小化到系统托盘") def browse_source_local(self): """浏览本地源目录""" path = filedialog.askdirectory(title="选择源目录") if path: self.source_local_path.set(path) self.log(f"源目录设置为: {path}") def browse_target_local(self): """浏览本地目标目录""" path = filedialog.askdirectory(title="选择目标目录") if path: self.target_local_path.set(path) self.log(f"目标目录设置为: {path}") def parse_smb_path(self, smb_path): """解析SMB路径""" if not smb_path: return None, None, None # 统一替换为斜杠 smb_path = smb_path.replace('\\', '/') # 移除开头的双斜杠或双反斜杠 if smb_path.startswith('//'): smb_path = smb_path[2:] # 分割路径 parts = smb_path.split('/', 2) if len(parts) < 2: return None, None, None server = parts[0] share = parts[1] path = parts[2] if len(parts) > 2 else '' # 确保路径以/开头 if path and not path.startswith('/'): path = '/' + path return server, share, path def test_source_smb_connection(self): """测试源SMB连接""" smb_path = self.source_smb_path.get() if not smb_path: messagebox.showwarning("警告", "请输入SMB路径") return server, share, path = self.parse_smb_path(smb_path) if not server or not share: messagebox.showwarning("警告", "SMB路径格式不正确,请参考示例格式") return username = self.source_smb_username.get() password = self.source_smb_password.get() self.test_smb_connection(server, share, username, password, "源") def test_target_smb_connection(self): """测试目标SMB连接""" smb_path = self.target_smb_path.get() if not smb_path: messagebox.showwarning("警告", "请输入SMB路径") return server, share, path = self.parse_smb_path(smb_path) if not server or not share: messagebox.showwarning("警告", "SMB路径格式不正确,请参考示例格式") return username = self.target_smb_username.get() password = self.target_smb_password.get() self.test_smb_connection(server, share, username, password, "目标") def test_smb_connection(self, server, share, username, password, label): """测试SMB连接""" try: self.log(f"正在测试{label}SMB连接: {server}/{share}") # 创建SMB连接 conn = SMBConnection( username if username else '', password if password else '', socket.gethostname(), server, domain='', use_ntlm_v2=True ) # 尝试连接 connected = conn.connect(server, 139) if connected: # 尝试列出共享内容 try: conn.listPath(share, '/') self.log(f"✓ {label}SMB连接测试成功") messagebox.showinfo("成功", f"{label}SMB连接测试成功!") except Exception as e: error_msg = f"无法访问共享目录: {str(e)}" self.log(f"✗ {error_msg}") messagebox.showerror("错误", error_msg) finally: conn.close() else: error_msg = f"无法连接到{label}SMB服务器" self.log(f"✗ {error_msg}") messagebox.showerror("错误", error_msg) except Exception as e: error_msg = f"{label}SMB连接测试失败: {str(e)}" self.log(f"✗ {error_msg}") messagebox.showerror("错误", error_msg) def setup_schedule_tab(self): """设置定时任务选项卡""" self.schedule_tab = ttk.Frame(self.notebook) self.notebook.add(self.schedule_tab, text="定时任务") # 启用定时任务 schedule_frame = ttk.LabelFrame(self.schedule_tab, text="定时设置", padding="15") schedule_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) self.enable_schedule = tk.BooleanVar(value=False) ttk.Checkbutton(schedule_frame, text="启用定时任务", variable=self.enable_schedule).pack(anchor=tk.W, pady=(0, 15)) # 频率和时间设置 settings_frame = ttk.Frame(schedule_frame) settings_frame.pack(fill=tk.X, pady=(0, 15)) # 频率选择 freq_frame = ttk.Frame(settings_frame) freq_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 20)) ttk.Label(freq_frame, text="频率:").pack(anchor=tk.W) self.schedule_freq = tk.StringVar(value="每天") freq_combo = ttk.Combobox(freq_frame, textvariable=self.schedule_freq, values=["每天", "每周"], width=10, state="readonly") freq_combo.pack(anchor=tk.W, pady=(5, 0)) # 每周选择 self.weekday_frame = ttk.Frame(freq_frame) self.weekday_frame.pack(anchor=tk.W, pady=(5, 0)) ttk.Label(self.weekday_frame, text="星期:").pack(side=tk.LEFT) self.weekday = tk.StringVar(value="星期一") weekday_combo = ttk.Combobox(self.weekday_frame, textvariable=self.weekday, values=["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"], width=10, state="readonly") weekday_combo.pack(side=tk.LEFT, padx=(5, 0)) self.weekday_frame.pack_forget() # 默认隐藏 # 时间选择 time_frame = ttk.Frame(settings_frame) time_frame.pack(side=tk.LEFT, fill=tk.Y) ttk.Label(time_frame, text="时间:").pack(anchor=tk.W) time_input_frame = ttk.Frame(time_frame) time_input_frame.pack(anchor=tk.W, pady=(5, 0)) self.schedule_hour = tk.StringVar(value="00") hour_combo = ttk.Combobox(time_input_frame, textvariable=self.schedule_hour, values=[f"{i:02d}" for i in range(24)], width=5, state="readonly") hour_combo.pack(side=tk.LEFT) ttk.Label(time_input_frame, text=":").pack(side=tk.LEFT, padx=(2, 2)) self.schedule_minute = tk.StringVar(value="00") minute_combo = ttk.Combobox(time_input_frame, textvariable=self.schedule_minute, values=[f"{i:02d}" for i in range(60)], width=5, state="readonly") minute_combo.pack(side=tk.LEFT) # 绑定频率变化事件 freq_combo.bind('<<ComboboxSelected>>', self.on_freq_change) # 添加定时任务按钮 ttk.Button(schedule_frame, text="添加定时任务", command=self.add_schedule_task).pack(anchor=tk.W, pady=(0, 15)) # 定时任务列表 list_frame = ttk.Frame(schedule_frame) list_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 15)) # 创建滚动条 list_scrollbar = ttk.Scrollbar(list_frame) list_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 创建列表框 self.task_listbox = tk.Listbox(list_frame, height=8, yscrollcommand=list_scrollbar.set) self.task_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) list_scrollbar.config(command=self.task_listbox.yview) # 删除任务按钮 ttk.Button(schedule_frame, text="删除选中任务", command=self.delete_selected_task).pack(anchor=tk.W, pady=(0, 5)) def on_freq_change(self, event=None): """频率选择变化""" if self.schedule_freq.get() == "每周": self.weekday_frame.pack(anchor=tk.W, pady=(5, 0)) else: self.weekday_frame.pack_forget() def add_schedule_task(self): """添加定时任务""" if not self.enable_schedule.get(): messagebox.showwarning("警告", "请先启用定时任务") return # 获取频率和时间 freq = self.schedule_freq.get() hour = self.schedule_hour.get() minute = self.schedule_minute.get() if freq == "每周": weekday = self.weekday.get() weekday_num = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"].index(weekday) task_desc = f"每周{weekday} {hour}:{minute}" task_data = { 'freq': 'weekly', 'weekday': weekday_num, 'hour': int(hour), 'minute': int(minute) } else: task_desc = f"每天 {hour}:{minute}" task_data = { 'freq': 'daily', 'hour': int(hour), 'minute': int(minute) } # 添加到列表 self.task_listbox.insert(tk.END, task_desc) self.scheduled_tasks.append(task_data) # 记录日志 self.log(f"已添加定时任务: {task_desc}") def delete_selected_task(self): """删除选中的定时任务""" selection = self.task_listbox.curselection() if not selection: messagebox.showwarning("警告", "请先选择一个任务") return index = selection[0] task_desc = self.task_listbox.get(index) self.task_listbox.delete(index) del self.scheduled_tasks[index] self.log(f"已删除定时任务: {task_desc}") def execute_copy_now(self): """立即执行复制""" # 在新线程中执行复制,避免界面卡顿 thread = threading.Thread(target=self.copy_files, daemon=True) thread.start() def copy_files(self): """执行文件复制""" try: self.log("=" * 60) self.log("开始复制文件...") start_time = datetime.now() self.log(f"开始时间: {start_time.strftime('%H:%M:%S')}") # 获取源文件和目标文件列表 source_files = self.get_source_files() if not source_files: self.log("警告: 没有找到要复制的文件") return self.log(f"找到 {len(source_files)} 个文件需要复制") # 执行复制 copied = 0 skipped = 0 errors = 0 for i, file_info in enumerate(source_files): try: success = self.copy_single_file(file_info) if success == "copied": copied += 1 if copied % 10 == 0: # 每10个文件记录一次进度 self.log(f"进度: 已处理 {i+1}/{len(source_files)} 个文件") elif success == "skipped": skipped += 1 elif success == "error": errors += 1 self.log(f"错误: 复制失败 - {file_info.get('rel_path', '未知文件')}") except Exception as e: errors += 1 self.log(f"异常: 复制文件时出错 - {str(e)}") # 完成 end_time = datetime.now() duration = end_time - start_time self.log(f"复制完成!") self.log(f"总计: {len(source_files)} 个文件") self.log(f"已复制: {copied} 个文件") self.log(f"跳过: {skipped} 个文件") self.log(f"错误: {errors} 个文件") self.log(f"耗时: {duration.total_seconds():.2f} 秒") self.log(f"完成时间: {end_time.strftime('%H:%M:%S')}") self.log("=" * 60) except Exception as e: self.log(f"严重错误: 复制过程中出现异常 - {str(e)}") def get_source_files(self): """获取源文件列表""" source_files = [] if self.source_type.get() == "local": # 本地目录 source_path = self.source_local_path.get() if not source_path or not os.path.exists(source_path): self.log(f"错误: 源目录不存在或未设置 - {source_path}") return [] self.log(f"扫描本地目录: {source_path}") for root, dirs, files in os.walk(source_path): for file in files: full_path = os.path.join(root, file) rel_path = os.path.relpath(full_path, source_path) source_files.append({ 'type': 'local', 'full_path': full_path, 'rel_path': rel_path }) else: # SMB目录 smb_path = self.source_smb_path.get() if not smb_path: self.log("错误: SMB源目录未设置") return [] server, share, base_path = self.parse_smb_path(smb_path) if not server or not share: self.log(f"错误: SMB路径格式不正确 - {smb_path}") return [] try: self.log(f"连接SMB服务器: {server}/{share}") conn = SMBConnection( self.source_smb_username.get() if self.source_smb_username.get() else '', self.source_smb_password.get() if self.source_smb_password.get() else '', socket.gethostname(), server, domain='', use_ntlm_v2=True ) if not conn.connect(server, 139): self.log(f"错误: 无法连接到SMB服务器 - {server}") return [] # 递归获取文件 self.log(f"扫描SMB目录: {base_path or '/'}") source_files.extend(self.get_smb_files_recursive(conn, share, base_path, '')) conn.close() except Exception as e: self.log(f"错误: 获取SMB文件列表失败 - {str(e)}") return source_files def get_smb_files_recursive(self, conn, share, base_path, rel_path): """递归获取SMB文件列表""" files = [] try: # 构建完整路径 full_path = base_path + '/' + rel_path if rel_path else base_path if not full_path.startswith('/'): full_path = '/' + full_path items = conn.listPath(share, full_path) for item in items: if item.filename in ['.', '..']: continue item_rel_path = os.path.join(rel_path, item.filename) if item.isDirectory: # 递归处理子目录 sub_files = self.get_smb_files_recursive(conn, share, base_path, item_rel_path) files.extend(sub_files) else: # 添加文件 files.append({ 'type': 'smb', 'conn': conn, 'share': share, 'base_path': base_path, 'rel_path': item_rel_path, 'filename': item.filename }) except Exception as e: self.log(f"警告: 无法读取SMB目录内容 - {str(e)}") return files def copy_single_file(self, file_info): """复制单个文件""" # 获取目标路径 target_path_info = self.get_target_path_info(file_info['rel_path']) # 检查目标文件是否存在 if self.copy_mode.get() == "skip" and self.target_file_exists(target_path_info, file_info['rel_path']): return "skipped" try: # 从源读取文件 if file_info['type'] == 'local': # 本地文件 with open(file_info['full_path'], 'rb') as f: file_data = f.read() else: # SMB文件 conn = file_info['conn'] share = file_info['share'] base_path = file_info['base_path'] rel_path = file_info['rel_path'] # 构建完整路径 full_smb_path = base_path + '/' + rel_path if base_path else '/' + rel_path if not full_smb_path.startswith('/'): full_smb_path = '/' + full_smb_path # 使用BytesIO接收文件数据 file_obj = io.BytesIO() try: conn.retrieveFile(share, full_smb_path, file_obj) file_data = file_obj.getvalue() finally: file_obj.close() # 写入目标 if self.target_type.get() == 'local': # 写入本地 target_path = target_path_info os.makedirs(os.path.dirname(target_path), exist_ok=True) with open(target_path, 'wb') as f: f.write(file_data) else: # 写入SMB server, share, base_path = target_path_info conn = SMBConnection( self.target_smb_username.get() if self.target_smb_username.get() else '', self.target_smb_password.get() if self.target_smb_password.get() else '', socket.gethostname(), server, domain='', use_ntlm_v2=True ) if not conn.connect(server, 139): return "error" # 构建完整SMB路径 full_smb_path = base_path + '/' + file_info['rel_path'] if base_path else '/' + file_info['rel_path'] if not full_smb_path.startswith('/'): full_smb_path = '/' + full_smb_path # 创建目录(如果需要) dir_path = os.path.dirname(full_smb_path) if dir_path and dir_path != '/': self.create_smb_directories(conn, share, dir_path) # 写入文件 file_obj = io.BytesIO(file_data) conn.storeFile(share, full_smb_path, file_obj) file_obj.close() conn.close() return "copied" except Exception as e: return "error" def get_target_path_info(self, rel_path): """获取目标路径信息""" if self.target_type.get() == 'local': base_path = self.target_local_path.get() return os.path.join(base_path, rel_path) else: # 对于SMB,返回解析后的信息 smb_path = self.target_smb_path.get() if not smb_path: return None, None, None server, share, base_path = self.parse_smb_path(smb_path) return server, share, base_path def target_file_exists(self, target_path_info, rel_path): """检查目标文件是否存在""" if self.target_type.get() == 'local': target_path = target_path_info return os.path.exists(target_path) else: server, share, base_path = target_path_info if not server or not share: return False try: conn = SMBConnection( self.target_smb_username.get() if self.target_smb_username.get() else '', self.target_smb_password.get() if self.target_smb_password.get() else '', socket.gethostname(), server, domain='', use_ntlm_v2=True ) if not conn.connect(server, 139): return False # 构建完整路径 full_smb_path = base_path + '/' + rel_path if base_path else '/' + rel_path if not full_smb_path.startswith('/'): full_smb_path = '/' + full_smb_path try: attrs = conn.getAttributes(share, full_smb_path) conn.close() return True except: conn.close() return False except: return False def create_smb_directories(self, conn, share, path): """创建SMB目录(递归)""" parts = path.strip('/').split('/') current_path = '' for part in parts: if not part: continue current_path = current_path + '/' + part if current_path else '/' + part try: conn.getAttributes(share, current_path) except: try: conn.createDirectory(share, current_path) except: pass def save_config(self): """保存配置(密码会被加密)""" config = { 'source_type': self.source_type.get(), 'source_local_path': self.source_local_path.get(), 'source_smb_path': self.source_smb_path.get(), 'source_smb_username': self.source_smb_username.get(), 'source_smb_password': self.encrypt_password(self.source_smb_password.get()), 'target_type': self.target_type.get(), 'target_local_path': self.target_local_path.get(), 'target_smb_path': self.target_smb_path.get(), 'target_smb_username': self.target_smb_username.get(), 'target_smb_password': self.encrypt_password(self.target_smb_password.get()), 'copy_mode': self.copy_mode.get(), 'auto_start': self.auto_start.get(), 'background_mode': self.background_mode.get(), 'scheduled_tasks': self.scheduled_tasks, 'config_version': '1.2' # 添加版本号,表示加密配置 } try: with open(self.config_file, 'w', encoding='utf-8') as f: json.dump(config, f, ensure_ascii=False, indent=2) self.log("配置已保存(密码已加密)") messagebox.showinfo("成功", "配置已保存。") except Exception as e: self.log(f"保存配置失败: {str(e)}") messagebox.showerror("错误", f"保存配置失败: {str(e)}") def load_config(self): """加载配置(密码会被解密)""" if not os.path.exists(self.config_file): return try: with open(self.config_file, 'r', encoding='utf-8') as f: config = json.load(f) # 检查配置版本 config_version = config.get('config_version', '1.0') # 加载配置到界面 self.source_type.set(config.get('source_type', 'local')) self.source_local_path.set(config.get('source_local_path', '')) self.source_smb_path.set(config.get('source_smb_path', '')) self.source_smb_username.set(config.get('source_smb_username', '')) # 解密密码 source_password = config.get('source_smb_password', '') if source_password and config_version >= '1.1': source_password = self.decrypt_password(source_password) self.source_smb_password.set(source_password) self.target_type.set(config.get('target_type', 'local')) self.target_local_path.set(config.get('target_local_path', '')) self.target_smb_path.set(config.get('target_smb_path', '')) self.target_smb_username.set(config.get('target_smb_username', '')) # 解密密码 target_password = config.get('target_smb_password', '') if target_password and config_version >= '1.1': target_password = self.decrypt_password(target_password) self.target_smb_password.set(target_password) self.copy_mode.set(config.get('copy_mode', 'overwrite')) # 加载程序设置 self.auto_start.set(config.get('auto_start', False)) self.background_mode.set(config.get('background_mode', False)) # 设置开机自启状态 if self.auto_start.get(): try: self.set_auto_start(True) except: pass # 如果设置失败,忽略 # 如果后台运行模式启用,创建托盘图标 if self.background_mode.get(): self.create_tray_icon() # 加载定时任务 tasks = config.get('scheduled_tasks', []) self.scheduled_tasks = tasks # 更新列表显示 self.task_listbox.delete(0, tk.END) for task in tasks: if task['freq'] == 'weekly': weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] weekday_str = weekdays[task['weekday']] self.task_listbox.insert(tk.END, f"每周{weekday_str} {task['hour']:02d}:{task['minute']:02d}") else: self.task_listbox.insert(tk.END, f"每天 {task['hour']:02d}:{task['minute']:02d}") # 更新UI状态 self.toggle_source_type() self.toggle_target_type() self.log("配置已从文件加载") except Exception as e: self.log(f"加载配置失败: {str(e)}") def log(self, message): """添加日志到日志框""" timestamp = datetime.now().strftime("%H:%M:%S") log_message = f"[{timestamp}] {message}" # 在主线程中更新UI self.root.after(0, self._add_log_message, log_message) def _add_log_message(self, message): """在主线程中添加日志消息""" self.log_text.insert(tk.END, message + "\n") self.log_text.see(tk.END) # 自动滚动到底部 def clear_log(self): """清除日志""" self.log_text.delete(1.0, tk.END) self.log("日志已清除") def start_schedule_thread(self): """启动定时任务线程""" self.running = True self.schedule_thread = threading.Thread(target=self.schedule_worker, daemon=True) self.schedule_thread.start() def schedule_worker(self): """定时任务工作线程 - 修复版""" while self.running: try: if not self.enable_schedule.get() or not self.scheduled_tasks: time.sleep(30) continue current_time = datetime.now() current_date = date.today() current_weekday = current_time.weekday() # 0=周一, 6=周日 current_hour = current_time.hour current_minute = current_time.minute for task in self.scheduled_tasks: # 检查时间是否匹配 if current_hour != task['hour'] or current_minute != task['minute']: continue # 检查今天是否应该执行 should_execute = False if task['freq'] == 'daily': # 每天执行 should_execute = True elif task['freq'] == 'weekly': # 每周特定星期执行 if current_weekday == task['weekday']: should_execute = True if should_execute: # 避免重复执行:检查今天是否已经执行过 execution_key = f"{current_date}_{task['hour']:02d}_{task['minute']:02d}" if task['freq'] == 'weekly': execution_key = f"{current_date}_{task['weekday']}_{task['hour']:02d}_{task['minute']:02d}" if hasattr(self, 'last_execution_day') and self.last_execution_day == execution_key: # 今天已经执行过 continue # 记录执行时间 self.last_execution_day = execution_key # 执行复制任务 self.log(f"定时任务触发: {self.get_task_description(task)}") self.execute_scheduled_copy() # 短暂休眠,避免重复检查 time.sleep(2) # 每分钟检查一次 time.sleep(60 - current_time.second) except Exception as e: self.log(f"定时任务检查出错: {str(e)}") time.sleep(30) def get_task_description(self, task): """获取任务描述""" if task['freq'] == 'weekly': weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] weekday_str = weekdays[task['weekday']] return f"每周{weekday_str} {task['hour']:02d}:{task['minute']:02d}" else: return f"每天 {task['hour']:02d}:{task['minute']:02d}" def execute_scheduled_copy(self): """执行定时复制""" # 在新线程中执行复制,避免界面卡顿 thread = threading.Thread(target=self.scheduled_copy_task, daemon=True) thread.start() def scheduled_copy_task(self): """定时复制任务""" self.copy_files() def on_closing(self): """关闭窗口事件""" if self.background_mode.get() and self.tray_icon: # 如果启用了后台运行且有托盘图标,则最小化到托盘 self.minimize_to_tray() else: # 否则正常退出 self.cleanup() self.root.destroy() def cleanup(self): """清理资源""" self.running = False self.save_config() # 关闭前自动保存配置 if self.tray_icon: self.tray_icon.stop() def main(): root = tk.Tk() app = SMBCopierApp(root) root.mainloop() if __name__ == "__main__": # 检查依赖库 try: import tkinter from tkinter import scrolledtext from smb.SMBConnection import SMBConnection import schedule from cryptography.fernet import Fernet import pystray from PIL import Image, ImageDraw except ImportError as e: print(f"缺少依赖库: {e}") print("请安装以下库:") print("pip install pysmb schedule cryptography pystray Pillow") sys.exit(1) main()
2025年12月18日
47 阅读
0 评论
0 点赞
2025-12-16
anolis8 安装cockpit管理平台 进行KVM虚拟机 podman容器管理
安装cockpit和kvm、podman管理插件yum install cockpit systemctl enable cockpit systemctl start cockpit yum install -y qemu-kvm libvirt virt-manager systemctl enable --now libvirtd yum install -y cockpit-machines yum install virt-viewer sudo firewall-cmd --permanent --zone=public --add-service=cockpit sudo firewall-cmd --reload yum install epel-release -y dnf install cockpit-podman -y使用系统用户登录控制台https://IP:9090/
2025年12月16日
43 阅读
0 评论
0 点赞
2025-12-16
彻底清理window10、windows11、Edge浏览器微软用户邮箱绑定
参考链接https://www.zhihu.com/question/8239136687https://www.azurew.com/%e9%94%99%e8%af%af%e8%a7%a3%e5%86%b3/13462.html清除与administrator绑定的微软账号作者:兔吮毫链接:https://www.zhihu.com/question/8239136687/answer/67329291890来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。步骤一:打开注册表,快捷键Win+R 输入 regedit ,点确定打开注册表步骤二:按路径找到地址(可以复制到地址栏一键直达),将这里的邮箱文件夹右键删除计算机\HKEY_CURRENT_USER\SOFTWARE\Microsoft\IdentityCRL\UserExtendedProperties计算机\HKEY_USERS.DEFAULT\Software\Microsoft\IdentityCRL\StoredIdentities步骤三:删除后注销电脑,重新登陆,继续下一步。步骤四:Win+R 输入regedit,打开注册表,找到地址,将 IdentityCRL 文件夹右键删除计算机\HKEY_CURRENT_USER\SOFTWARE\Microsoft\IdentityCRL计算机\HKEY_USERS.DEFAULT\Software\Microsoft\IdentityCRL步骤五:注销电脑,重新登陆,进入设置账户,你可以看到删除出现了,它终于出现了至此,Administrator与微软的关系解绑了。步骤六:删除登录密码,登录选项-密码-更改-输入当前密码,新密码不写直接点完成清除Edge浏览器微软用户绑定当前用户下的edge浏览器用户数据。如我的当前用户为"administrator",位置应该是:"C:\Users\Administrator\AppData\Local\Microsoft\Edge"删除Edge目录和Edgexx开头的目录
2025年12月16日
162 阅读
0 评论
0 点赞
2025-12-08
PostgreSQL启用密码 网段白名单 配置文件热加载 pg_ctl reload
部署postgresql数据库yum install postgresql-server查看数据库版本psql --version psql (PostgreSQL) 13.14初始化数据库postgresql-setup initdb开启数据库密码验证,取消postgresql.conf配置文件中的注释/var/lib/pgsql/data/postgresql.conf password_encryption = md5 # md5 or scram-sha-256如果数据库不在本地,要通过网络访问,则需修改监听地址和防火墙开放对应端口/var/lib/pgsql/data/postgresql.conf listen_addresses = '*' # what IP address(es) to listen on; port = 5432配置数据库访问策略,允许本地网络使用密码访问数据库/var/lib/pgsql/data/pg_hba.conf # TYPE DATABASE USER ADDRESS METHOD # IPv4 local connections: host all all 127.0.0.1/32 md5 # IPv6 local connections: host all all ::1/128 md5 如果要允许所有外部IP访问,地址要写all host all all all md5启动数据库systemctl start postgresql systemctl enable postgresql配置文件热加载 pg_ctl reload,热加载不终端服务器root# su - postgres postgres$ pg_ctl reload -D /var/lib/pgsql/data 提示server sianaled代表成功
2025年12月08日
37 阅读
0 评论
0 点赞
2025-12-04
深信服下一代防火墙、深信服全网行为管理设备密码重置 U盘 交叉线
1. U盘恢复密码从标准版本12.0.42开始,AC/SG设备支持通过U盘恢复密码。具体步骤如下:新建一个txt文档,重命名为reset-cfg.txt,并将其拷贝到U盘根目录,U盘格式需为FAT32。确保电脑和设备可以通信,访问设备地址 https://ACIP/php/rp.php,网页会提示“创建文件成功,请连接交叉线并重启设备”。请确保有“创建文件成功”的提示。将U盘插入设备,拔掉所有网口网线(记住接线信息),然后重启设备。等待大约10分钟后,拔出U盘,尝试使用默认帐号admin和密码admin登录设备。注意:U盘恢复密码完成后,建议手动重启一次设备[1]。2. 交叉线恢复密码如果您的设备版本为AC/SG5.5R1及以上,可以使用交叉线恢复密码。步骤如下:确保电脑和设备可以通信,访问设备地址 https://ACIP/php/rp.php,确保有“创建文件成功”的提示。准备交叉线短接设备任意两个非bypass电口,拔掉其他网口网线,只保留短接的交叉线。手动重启设备,等待大约10分钟后,拔掉交叉线,尝试使用默认帐号admin和密码admin登录设备[1]。3. 通过备份配置或管理员恢复密码如果您有备份的配置,或者有administrator角色的二级管理员,可以提供加盖公司公章的密码重置申请函,协调工程师协助恢复密码。如果没有备份配置且需要配置,建议使用上面提到的U盘或交叉线方式恢复密码实测方法二无法重置,使用方法一重置成功
2025年12月04日
59 阅读
0 评论
1 点赞
2025-11-26
VeeamBackup&Replication12高级技巧 NAS存储 任务异地备份 Backup Copy Job
1、添加仓库Backup Infrastructure - Backup Repositories - Add Repository可添加的类型有Direct attatched storage 直连存储Network attached storage 网络存储Deduplicating storage appliance 去重存储object storage 对象存储veeam data cloud vault veeam数据云选择网络存储可添加NFS SMB可添加群晖SMB2、创建Backup Copy Job任务Home - Backup Copy可选Image-level backup、Application-level backupImmediate copy 为备份后立即复制Periodic copy 为定期复制备份目标可以为jobs任务或repositories仓库可配置存储位置和策略配置完成
2025年11月26日
42 阅读
0 评论
0 点赞
2025-11-20
VeeamBackup&Replication12高级技巧使用veeam-recovery异机恢复SUSE Linux Enterprise Server 15 SLES15
一、问题描述某物理数据库服务器,使用SUSE Linux Enterprise Server 15 (SLES15)系统,具备系统盘和FC存储,数据库运行于FC存储中;服务器整机通过Veeam备份,还原到虚拟机中进行测试时,由于磁盘路径变动,系统无法启动,经过多轮测试后成功启动系统本文记录异机恢复后的系统修改过程二、工具准备与流程Veeam备份服务器信息Veeam恢复镜像 veeam-recovery-amd64-6.0.0.iso下载地址 https://repository.veeam.com/backup/linux/agent/veeam-recovery-media/x64/SLES15安装镜像,具备救援模式 SLE-15-SP3-Full-x86_64-GM-Media1.iso整体流程配置虚拟机-使用Veeam恢复镜像还原系统,正确映射分区-启动SLES15救援模式,识别lvm,修改fstab,修改网络配置-启动系统-测试业务三、恢复系统1、创建虚拟机2、挂载并进入veeam-recovery恢复模式3、配置网络配置网络调用的nmtui,配置为能够和veeam通信的IP即可4、进行恢复从Veeam服务器恢复登陆服务器,选择要恢复的备份5、映射磁盘务必注意,先将5T磁盘映射为原447.1G系统盘,free剩余空间映射1.99T的LVM卷此时lvm卷会显示为sda4(lvm),并下方显示VG和LV卷为/hana分区6、进行恢复按S 启动恢复即可 Start restore数据量越大恢复越慢四、启动救援模式恢复完成启动系统后,会卡在启动项无法进入系统挂载SLE-15-SP3-Full-x86_64-GM-Media1.iso,进入恢复模式挂载系统进行修复查看当前磁盘情况fdisk -l挂载磁盘 mount /dev/sda3 /mnt/ mount /dev/sda1 /mnt/boot/efi mount -o bind /dev /mnt/dev mount -o bind /proc /mnt/proc mount -o bind /sys /mnt/sys 进入chroot模式 chroot /mnt 挂载lvm卷 扫描pv pvscan 扫描vg vgscan 激活vg卷 vgchange -ay VG 扫描逻辑卷 lvscan 修复fstab为当前实际盘符 vi /etc/fstab 禁用多路径服务 systemctl disable multipathd 重建initramfs mkinitrd -f 配置网络 yast2 选择System-Network Settings 删除旧网卡,按Overview-DNS-Routing分别配置 注意:yast2很不好用是正常的,他不如nmtui操作简单 重启五、连接测试启动系统桌面黑屏,但ssh连接正常数据库工具连接数据库测试正常恢复试验成功六、一点小问题虽然SSH和业务系统都已经正常了,但是进不去桌面,还需要进一步排查原因桌面服务不影响系统和业务运行七、桌面显示问题解决了AI分析解决的,是因为原机器是第三代志强可扩展,测试虚拟机是第一代志强可扩展,导致的软件不能启动,可以临时修改为softpipe启动。根据提供的日志,我们可以看到gnome-shell不断崩溃并重启。日志中关键的错误信息包括: "traps: llvmpipe-0[3913] trap invalid opcode ip:7f296830a80c sp:7f29deaff560 error:0" "gnome-shell-x11.service: Main process exited, code=killed, status=4/ILL" "JS WARNING: [resource:///org/gnome/shell/ui/layout.js 24]: reference to undefined property "MetaWindowX11"" 其中,invalid opcode(非法指令)和ILL(非法指令信号)表明gnome-shell在尝试执行一个当前CPU不支持的指令。这通常是由于CPU微架构不兼容导致的,比如在迁移后新服务器的CPU与旧服务器不同,可能新CPU较老,不支持某些指令集(如SSE4.2、AVX等),而gnome-shell或其中的组件(如llvmpipe)是使用这些指令集编译的。 另外,llvmpipe是Mesa软件渲染器,它使用LLVM进行即时编译,可能会生成针对特定CPU的优化代码。如果迁移后的服务器CPU不支持这些指令,就会导致非法指令错误。 我们按照以下步骤操作: 通过SSH连接到服务器。 切换到root用户。 编辑/etc/environment,添加一行: GALLIUM_DRIVER=softpipe 重启系统。 如果重启后图形界面能够正常启动,那么问题就解决了。
2025年11月20日
83 阅读
0 评论
0 点赞
1
2
...
24