pyinstaller打包的python exe程序实现自动更新

点击上方蓝字[协议分析与还原]关注我们


近期在为一系列python工具exe程序加自动更新功能,方便管理维护及分发。

近期沉迷于python的开发,积累了一系列python工具,需要部署到其它机器上,部署的过程都是拷贝,拷贝一次也就罢了,一碰到程序没写好,需要升级,就很麻烦了,得再拷贝一次,说不定还拷错了,烦死个人。

于是决定做个能够匹配我工具升级需求的自动更新工具,每台待安装电脑的第一次手动安装完整程序,之后每次程序启动的时候自动升级,从服务器上下载升级包,覆盖旧程序,完成升级。

由于我的python工具的特殊性,是由pyinstaller打包成exe的py文件以及一批网页文件组成,因此升级包需要综合考虑,不能顾头不顾腚。

01

自动更新框架

毫无疑问,我们的整个程序的更新应该是C/S架构,服务端存放安装包和版本更新控制服务,客户端每次启动时与服务器通信检查更新,如果有更新则用更新替换现有版本,之后进入工具主流程。

为了客户端的灵活性,我们选择客户端启动更新与客户端主体功能分离,二者独立实现,在启动更新部分实现自动更新功能并完成主体功能的启动,主体功能使用单独的进程实现,这样自动更新就只需要关心更新部分,相对稳定,而工具主体部分,则可随意根据项目调整。

大概就是这样:

32c1a0be4fd5f377c5e1a3b4356d6b0a.png

框架基本上就是这样了,一个服务端程序,一个启动程序,一个主体程序。服务端程序另外开发,我直接用php开发了个简单的服务接口,校验请求参数及返回程序升级包即可,启动程序负责更新启动部分,主体程序负责工具主体部分,二者用python开发,用脚本统一打包。

02

启动程序

启动程序负责与服务器联系,进行自动更新,自动更新完毕启动主体程序。

启动程序大概功能实现如下:

def main(process_name):
    controlstop_process(process_name)  # 停止exe
    current_dir = os.getcwd()
    local_exe_path = os.path.join(current_dir, process_name)
    update_exe(local_exe_path)  # 更新exe及相关文件,删除重新下载
    startexe(local_exe_path)  # 启动新exe

首先停止已启动的主体程序,咱要专注更新。

def controlstop_process(process_name):
    current_dir = os.getcwd()
    for proc in psutil.process_iter(['name']):
        if proc.info['name'] == process_name:
            proc.terminate()
            print(f"进程 {process_name} 已终止")
            return

接下来进行更新过程,检查更新并进行更新,更新主体程序exe及相关文件,为了更好地实现,直接删除当前目录下的这些内容。检查更新过程中,获取主体程序exe的版本参数到服务器比对,需要更新则传回文件内容。

def update_exe(local_exe_path):
    ret, tmppath = checkupdate(local_exe_path)
    if ret and len(tmppath) > 0:  
        current_dir = os.getcwd()
        delete_all_but_startmain()
        tmpdatapath = os.path.join(tmppath, 'dist')
        shutil.copytree(tmpdatapath, current_dir, dirs_exist_ok=True)
        shutil.rmtree(tmppath)#删除临时目录

下载的更新内容zip,我们在这里直接写临时目录然后解压,返回解压后路径供上面的更新函数使用。

respdata = ret.content
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
    temp_file.write(respdata)
    temp_file_path = temp_file.name
# 创建临时子目录
temp_subdir = tempfile.mkdtemp()
# 解压到临时子目录
with zipfile.ZipFile(temp_file_path, 'r') as zip_ref:
    for file_info in zip_ref.infolist():
        file_info.filename=file_info.filename.encode('cp437').decode('gbk')
        zip_ref.extract(file_info,temp_subdir)
    # zip_ref.extractall(temp_subdir)
# 删除临时文件
os.remove(temp_file_path)
# shutil.rmtree(temp_subdir)
return True, temp_subdir

最后当主体程序更新完毕,就是启动主体程序了。

03

打包exe脚本

主体程序和启动程序的python代码是放在一起的,为方便打包,当然要用一个脚本来统一搞更合适,何况还有些网页相关文件需要一起打包。因为启动程序后续不更新,为方便管理,除第一次外,我们将主体程序和其它网页相关文件放一个压缩包打包发布,而启动程序单独发布。

这个过程中,exe的生成当然使用pyinstaller了,不过,新版本pyinstaller无法加key,有点小小的遗憾,如果需要稍微加点形同虚设的防护,估计得用老版本的pyinstaller,如果想真正防护起来,防止被逆向破解,要么用cython,要么换一种真正的编程语言了。

一定要记住,python是脚本语言。

一个简单的bat脚本就是这样:

@echo off
chcp 65001
if exist "dist\haha.exe" (
    del "dist\haha.exe"
)
if exist "dist\startmain.exe" (
    del "dist\startmain.exe"
)
pyinstaller  --version-file=version.txt -F -n "haha.exe" main.py
xcopy  "haha.html" dist /Y
::设置变量
set source_folder=css
set target_folder=dist\css


::检查目标文件夹是否存在,不存在则创建
if not exist "%target_folder%" (
    md "%target_folder%"
)


::复制文件夹
xcopy "%source_folder%" "%target_folder%" /S /E /Y


::设置变量
set source_folder=js
set target_folder=dist\js


::检查目标文件夹是否存在,不存在则创建
if not exist "%target_folder%" (
    md "%target_folder%"
)


::复制文件夹
xcopy "%source_folder%" "%target_folder%" /S /E /Y


::设置变量
set source_dir=dist\*
set zip_file=dist\haha.zip


::检查源目录是否存在
if not exist "%source_dir%" (
    echo 源目录不存在!
    pause
    exit /b
)


::创建zip文件
"zip.exe" -r %zip_file% "%source_dir%"


echo "打包完成!"
::startmain不打包
pyinstaller --version-file=version.txt -F -n "startmain.exe" startmain.py

打包得到的haha.zip,就是最终放到服务器上去的升级包了,而startmain.exe则是启动程序,记住,这个程序里面会删当前目录下的其它文件,所以,一定要把它放单独文件夹,防止删库跑路,当然,这是一个码农的得道之路,也不是不能做。

04

服务端程序

要问世界上最好的语言是什么,毫无疑问,当然是PHP了,大家请大声说出来:PHP是世界上最好的语言。

我们的服务端程序,当然要用PHP来写,先想好怎么实现。

我们有一个版本管理文件,里面有需要维护的升级包的信息,包括应用名称、版本、大小、哈希、存储路径、升级包名称、是否强制更新,当然,支持管理多个工具的版本。

版本管理文件长这样:

{
    "apps": [
              {
                "app_id": "haha",
                "version": "1.2.3",
                "reset": "1",//1 强制更新,0 正常更新
                "hash": "e10adc3949ba59abbe56e057f20f883e", // 文件哈希,根据实际情况选择算法
                "size": 123456, // 文件大小,单位字节
                "path": "updates/",
                "update_file": "haha_1.2.3.zip" // 更新文件
              },
              {
                "app_id": "hehe",
                "version": "1.2.3",
                 "reset": "0",
                "hash": "e10adc3949ba59abbe56e057f20f883e", // 文件哈希,根据实际情况选择算法
                "size": 123456, // 文件大小,单位字节
                "path": "updates/",
                "update_file": "hehe_1.2.3.zip" // 更新文件
              }
    ]
}

之后,就是我们的PHP大佬出马了,利用这个文件来进行版本的比对,决定是否给客户端更新:

<?php
  $appId=$_POST['app'];
  $version= $_POST['version'];
  // 版本信息文件路径
  $versionFile = 'version.txt';
  $contents = file_get_contents($versionFile);
  $pattern = '/\/\/.*\n/';
  $contents = preg_replace($pattern, '', $contents);
  // 读取当前版本信息
  $versionInfo = json_decode($contents, true);
  // 查找对应App的信息
  foreach ($versionInfo['apps'] as $app) {
    if ($app['app_id'] === $appId) {
      // 找到对应App,进行版本比较
      if ( "0"!==$app['reset'] or $version!==$app['version'] ) {
        // 版本不一致,返回更新信息
        $filePath = $app['path'] . $app['update_file'];
        if (file_exists($filePath)) {
          header('Content-Type: application/zip'); // 根据文件类型设置
          header('Content-Disposition: attachment; filename="' . $appInfo['update_file'] . '"');
          header('Content-Length: ' . filesize($filePath));
          readfile($filePath);
          exit;
        } else {
          // 文件不存在
          http_response_code(404);
          echo '更新文件不存在';
          exit;
        }
      } else {
        // 版本一致,无需更新
        http_response_code(200);
        echo '版本一致,无需更新';
        exit;
      }
    }
  }
  http_response_code(200);
  echo '没有app,无需更新';
?>

短短几行,PHP就完成了我们需要的功能,棒不棒。

05

JAVA在诋毁同行

一位PHP程序员去面试,面试官问:“你为什么选择PHP?”

程序员答:“因为PHP是世界上最好的语言,它能让我用最少的代码写出最多的bug!”

bcdf1fdf1a71f339d7ef6be8774fd035.gif

新的规则,及时收推文要先给公号星标

别忘了星标一下,不然就错过了

7cb0e2abaa796915c4c263a4e755dd92.jpeg

长按进行关注,时刻进行交流。

作者:多姿多彩

物联沃分享整理
物联沃-IOTWORD物联网 » pyinstaller打包的python exe程序实现自动更新

发表回复