使用electron-release-server+autoUpdater更新electron程序😉😊😄

2,986 阅读2分钟

前言

目前electron程序主流的打包方式是使用electron-builder,同时使用electron-updater进行版本更新,但是本人所在团队目前新开发的一个electron项目技术方案为Electron Forge+ React + TypeScript,采用Electron Forge打包,打包方式不一样导致版本更新方式也不一样,也就是项目没法使用electron-updater进行版本更新😟😟😟,根据Electron Forge官方文档中介绍的版本更新方式,最终决定采用服务端搭建electron-release-server+客户端使用autoUpdater进行版本更新。

Deploying an Update Server

If you're developing a private Electron application, or if you're not publishing releases to GitHub Releases, it may be necessary to run your own update server.

Depending on your needs, you can choose from one of these:

  • Hazel – Update server for private or open-source apps which can be deployed for free on Vercel. It pulls from GitHub Releases and leverages the power of GitHub's CDN.
  • Nuts – Also uses GitHub Releases, but caches app updates on disk and supports private repositories.
  • electron-release-server – Provides a dashboard for handling releases and does not require releases to originate on GitHub.
  • Nucleus – A complete update server for Electron apps maintained by Atlassian. Supports multiple applications and channels; uses a static file store to minify server cost.

此处先放上效果图:

Snap16.png

Snap17.png

搭建electron-release-server

  • 从GitHub上下载electron-release-server源码
git clone https://github.com/ArekSredzki/electron-release-server.git

本机运行

本机环境:macOS Big Sur,版本11.5.2,Apple M1芯片

安装完成后将Postgresql路径添加到PATH环境变量,如下:

vi /etc/profile
export PATH=${PATH}:/Library/Postgresql/11/bin
source /etc/profile
  • 打开SQL Shell(psql),执行以下命令:
CREATE ROLE electron_release_server_user ENCRYPTED PASSWORD '123456' LOGIN;

CREATE DATABASE electron_release_server OWNER "electron_release_server_user";

CREATE DATABASE electron_release_server_sessions OWNER "electron_release_server_user";

启动Postgresql的命令如下:

pg_ctl -D /Library/Postgresql/11/data start
  • 修改config.js
cd electron-release-server
cp config/local.template config/local.js
vi config/local.js

修改后的config.js如下:

module.exports = {
   appUrl: 'http://localhost:80',
   
   auth: {
      static: {
        username: 'admin',
        password: '123456'
      },
   },
   
   jwt: {
     token_secret: 'EB9F0CA4414893F7B72DDF0F8507D88042DB4DBF8BD9D0A5279ADB54158EB2F0'
   },
   
   connections: {
     postgresql: {
        adapter: 'sails-postgresql',
        host: 'localhost',
        user: 'electron_release_server_user',
        password: '123456',
        database: 'electron_release_server'
     }
   },

   session: {
     secret: 'EB9F0CA4414893F7B72DDF0F8507D88042DB4DBF8BD9D0A5279ADB54158EB2F0',
     database: 'electron_release_server_sessions',
     host: 'localhost',
     user: 'electron_release_server_user',
     password: '123456',
     port: 5432
   },
   
   files: {
     dirname: '/Users/kongpf/Desktop/electron-release-server/releases',
   }
};
字段说明
appUrl网站地址,如http://localhost:80
username管理员的用户名,如admin
password管理员的密码,如123456
token_secretjwt生成token的secret,推荐为63个随机字母或数字
host主机名,当前机器即为localhost
user数据库用户名,此处为electron_release_server_user
password数据库密码,此处为123456
database数据库名称,此处为electron_release_server或electron_release_server_sessions
port数据库端口,此处为默认端口5432
dirname存放上传文件的路径,根据自己的机器情况,设置为合适的值
  • 设置session
mkdir sails_pg_session
cd sails_pg_session
git clone https://github.com/ravitej91/sails-pg-session.git .
npm update
psql electron_release_server_sessions < ./sql/sails-pg-session-support.sql postgres
  • 安装运行
//安装依赖
npm install -g sails
npm install
//开发环境运行
npm start
//生产环境运行
npm start --prod

注意:node版本要为10,如10.16.0,版本太高程序运行不起来,bower.json文件中"bootstrap-sass-official": "^3.3.7"改为"bootstrap-sass-official": "3.3.7"

程序运行成功后在浏览器里访问地址http://localhost:80,就可看到以下页面:

index.png

docker运行

  • 安装docker
  • 修改docker-compose.yml文件 主要修改APP_USERNAME(管理员用户名),APP_PASSWORD(管理员密码),TOKEN_SECRET(JWT Secret)信息,修改后的配置如下:
version: '2'

services:

web:

build: .

environment:

APP_USERNAME: admin

APP_PASSWORD: 123456

DB_HOST: db

DB_PORT: 5432

DB_USERNAME: releaseserver

DB_NAME: releaseserver

DB_PASSWORD: secret

TOKEN_SECRET: EB9F0CA4414893F7B72DDF0F8507D88042DB4DBF8BD9D0A5279ADB54158EB2F0

APP_URL: 'localhost:5000'

ASSETS_PATH: '/usr/src/electron-release-server/releases'

depends_on:

- db

ports:

- '5000:80'

entrypoint: ./scripts/wait.sh db:5432 -- npm start

volumes:

- ./releases:/usr/src/electron-release-server/releases

db:

image: postgres:11

environment:

POSTGRES_PASSWORD: secret

POSTGRES_USER: releaseserver

volumes:

- ./postgresql:/var/lib/postgresql/data

  • 构建镜像
docker-compose up -d

构建成功后在浏览器里访问地址http://localhost:5000,就可看到以下页面:

index.png

注意:构建过程中要访问Github下载依赖,很可能下载依赖失败导致构建失败,贴心的我已经帮你打包好了镜像并上传DockerHub,替换docker-compose.yml文件中的build: .为image: rainboy2010/electron-release-server:latest即可

修改后的electron-release-server代码已经上传到GitHub,地址: github.com/kongpf8848/…

上传新版本

1.以Admin登录,用户名和密码为之前配置的admin/123456

Admin.png

2.在版本管理页面,添加版本信息

Add New Version.png

字段说明
VersionName版本名称,如1.0.1
Flavor风味,取默认值default即可
Channel渠道,取默认值stable即可
Change Notes更新日志

3.添加Asset

More

Add Asset.png

1.0.1.png

Platform可取的值为:

  • Windows 64 bit
  • Windows 32 bit
  • OSX 64 bit Intel
  • OSX 64 bit ARM
  • Linux 64 bit
  • Linux 32 bit

macOS程序更新需上传.dmg和.zip文件,Windows程序更新需上传.exe和.nupkg文件

以macOS系统为例,上传.dmg和.zip文件后,页面如下:

44.png

客户端代码

  • 编写一个update.js文件,用于封装版本更新逻辑,代码如下:
//update.js
import {app,BrowserWindow,ipcMain,dialog,autoUpdater} from 'electron'

export class UpdateManager {

    private static instance: UpdateManager = new UpdateManager();

    public static getInstance() {
       return this.instance;
    }

    baseUrl="http://localhost:5000"
    mainWindow:BrowserWindow=null;

    sendUpdateMessage(text:any) {
      this.mainWindow?.webContents.send('update-message', text)
    }

    init(window:BrowserWindow){

       console.log("ipcMain-update,init")
       this.mainWindow=window
       var platform=""
       var arch=""

       if(process.platform === 'darwin'){
          platform="osx"
          if(process.arch === 'arm64'){
             arch='arm64'
          }else{
             arch='64'
          }

        }else if(process.platform === 'win32'){
          platform="windows"
          if(process.arch === 'x64'){
             arch='64'
          }else{
             arch='32'
          }
        }else if(process.platform === 'linux'){
          platform="linux"
          if(process.arch === 'x64' || process.arch === 'x86_64'){
             arch='64'
          }else{
             arch='32'
          }
        }

        var feedUrl=this.baseUrl+"/update/"+platform+"_"+arch+"/"+app.getVersion()+"/stable"
        autoUpdater.setFeedURL({url: feedUrl});
        
        autoUpdater.on('error', error=> {
          console.log("ipcMain-update,error:"+error)
          this.sendUpdateMessage({cmd: 'error',message: error})
        });
        
        autoUpdater.on('update-not-available',()=>{
          console.log("ipcMain-update,update-not-available")
          this.sendUpdateMessage({cmd: 'update-not-available',message: 'has not found any updates'})
        });

        autoUpdater.on('update-available',()=> {
          console.log("ipcMain-update,update-available")
          this.sendUpdateMessage({cmd: 'update-available',message: 'find new version'})
        });

        autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
          console.log("ipcMain-update,update-downloaded:"+event)
          this.sendUpdateMessage({cmd: 'update-downloaded',message:{
            releaseNotes:releaseNotes,
            releaseName:releaseName
         }})

         const dialogOpts = {
             type: 'info',
             buttons: ['确定', '稍后'],
             title: '版本更新',
             message: "发现新版本:"+releaseName,detail: '新版本已经下载完成,是否现在重启?'
          }
          dialog.showMessageBox(dialogOpts).then((returnValue) => {
             if (returnValue.response === 0) {
                autoUpdater.quitAndInstall()
          }
        })
     })

     ipcMain.on("checkForUpdate", (event, args) => {
        console.log("ipcMain-update,checkForUpdate")
        autoUpdater.checkForUpdates();
     })
  }
}

feedUrl的格式如下: ${baseUrl}/update/${platform}/${version}/${channel},如:

http://localhost:5000/update/osx_64/0.0.1/stable

http://localhost:5000/update/osx_arm64/0.0.1/stable

http://localhost:5000/update/windows_64/0.0.1/stable

http://localhost:5000/update/windows_32/0.0.1/stable

  • 在主进程中初始化版本更新
//main.js
let mainWindow: BrowserWindow
const createWindow = (): void => {
   mainWindow = new BrowserWindow({
     width: 1200,
     height: 600,
     minWidth: 700,
     minHeight: 350,
     webPreferences: {
       nodeIntegration: true,
       contextIsolation: true
     }
   });
   
   ......
   
   if(app.isPackaged) {
     UpdateManager.getInstance().init(mainWindow)
   }
};

app.on('ready', createWindow);
  • 在渲染进程中检查新版本,如下:
//render.js
window.addEventListener('DOMContentLoaded', () => {
   ipcRenderer.on("update-message",function(event,args){
     console.log("ipcRenderer,update-message,message:"+JSON.stringify(args.message))
     if("update-not-available" ===args.cmd){
        console.log("ipcRenderer,update-not-available")
     }else if("update-available" ===args.cmd){
        console.log("ipcRenderer,update-available")
     }else if("update-downloaded" ===args.cmd){
        console.log("ipcRenderer,update-downloaded")
     }else if("error" ===args.cmd){
        console.log("ipcRenderer,error")
     }
   });
  setTimeout(() => {
    console.log("ipcRenderer,checkForUpdate")
    ipcRenderer.send("checkForUpdate");
  },3000);
});

当检查到有版本更新时,app会自动从远程服务器下载更新包,下载完成后会弹窗提示用户安装,如下:

macOS系统Windows系统
macOS.pngWindows.png

注意:使用autoUpdater更新程序时,发现新版本后app会立即下载,中间没办法暂停下载,下载过程中也没有下载进度回调😒😓😨

参考资源