这篇文章是Habré文章第一部分的续篇,其中详细介绍了在MS Windows上部署Django堆栈的情况。接下来,我们将提供创建安装程序的分步说明,该安装程序将自动在其他计算机上安装堆栈的过程,而无需在命令行中进行操作,创建虚拟机等,其中整个操作序列将简化为操作Next-> Next-> Finish。
所以安装程序应该做什么:
- 将所有必要的程序和组件解压缩到用户指定的目录中。
- 执行安装前检查。
- 在Windows注册表中注册Python解释器。
- 如果尚未安装,请安装软件依赖项库。
- 创建Apache和PostgreSQL服务,然后启动它们。
- 另外一个好处是自动创建了一个卸载程序,如果用户需要,它将删除已安装的堆栈。
在安装程序的可能选项中,我们将选择免费的安装程序Inno Setup,因为 它允许您执行上述所有操作,从而使您无需运行大量脚本即可创建安装程序。与Wix相比,安装文件的语法为ini格式,比xml更易于阅读和更改。如今,它在功能和稳定性方面可与甚至超过许多商业安装程序竞争。
最重要的是,创建基本的安装程序根本不需要编写脚本,因为Inno Setup带有图形向导,可以出色地完成基本的安装程序。
安装逻辑可以用Pascal语言编写,而不是用Wix进行复杂的自定义操作。它的唯一缺点是它仅创建exe,不支持msi文件格式。
步骤1.Inno Setup安装
此处不需要其他注释,因为 下载和安装安装程序很简单。
步骤2:编写Inno Setup安装脚本
让我们使用安装脚本向导创建一个存根Inno Setup脚本(* .iss文件)。
结果,将创建一个* .iss文件,其中包含以下内容:
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "Severcart"
#define MyAppVersion "1.21.0"
#define MyAppPublisher "Severcart Inc."
#define MyAppURL "https://www.severcart.ru/"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{4FAF87DC-4DBD-42CE-A2A2-B6D559E76BDC}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName=c:\severcart
DefaultGroupName={#MyAppName}
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
OutputDir=C:\Users\Developer\Desktop\Output
OutputBaseFilename=mysetup
Compression=lzma
SolidCompression=yes
WizardStyle=modern
[Languages]
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"
[Files]
Source: "C:\severcart\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
步骤3.安装前检查
在将程序解压缩到目录并更改注册表之前,您需要检查TCP端口是否对Apache和PostgreSQL可用,并且还需要检查Windows的最低系统要求,因为如本文第一部分所述,已安装的Python版本只能从MS Windows 8版本(内核版本6.2)开始工作。
要执行必要的检查,让我们使用安装文件的[Code]部分。第[代码]是定义Pascal脚本的可选部分。Pascal脚本可用于以不同方式自定义安装或卸载。请注意,创建Pascal脚本并不容易,并且需要具有Pascal或至少类似编程语言的Inno Setup和编程技能的经验。 为了检查TCP端口的可用性,我们将创建以下函数: 我们将在InitializeSetup函数中调用测试函数,该函数在安装初始化期间被调用。返回False取消安装,否则返回True。
function IsWindowsVersionOrNewer(Major, Minor: Integer): Boolean;
var
Version: TWindowsVersion;
begin
GetWindowsVersionEx(Version);
Result := (Version.Major > Major) or ((Version.Major = Major) and (Version.Minor >= Minor));
end;
function IsWindows8OrNewer: Boolean;
begin
Result := IsWindowsVersionOrNewer(6, 2);
end;
function CheckPortOccupied(Port:String):Boolean;
var
ResultCode: Integer;
begin
Exec(ExpandConstant('{cmd}'), '/C netstat -na | findstr'+' /C:":'+Port+' "', '',0,ewWaitUntilTerminated, ResultCode);
if ResultCode <> 1 then
begin
Log('this port('+Port+') is occupied');
Result := True;
end else
begin
Result := False;
end;
end;
function InitializeSetup(): Boolean;
var
port_80_check, port_5432_check: boolean;
begin
if not IsWindows8OrNewer() then begin
MsgBox(' . Windows 2012 Windows 8.0.',mbError,MB_OK);
Abort();
Result := False;
end;
port_80_check := CheckPortOccupied('8080');
if port_80_check then begin
MsgBox(' . TCP 8080 .',mbError,MB_OK);
Abort();
Result := False;
end;
port_5432_check := CheckPortOccupied('5432');
if port_5432_check then begin
MsgBox(' . TCP 5432 .',mbError,MB_OK);
Result := False;
Abort();
end;
Result := True;
步骤4.在Windows注册表中注册Python
此可选部分定义了安装程序必须在用户系统上创建或修改的所有注册表项/值。
为此,添加PYTHONPATH和PYTHONHOME键并更新Path变量。
sys.path包含一个字符串列表,这些字符串为将来的Python项目提供了模块和软件包的搜索位置。它是从PYTHONPATH环境变量和其他设置初始化的。
PYTHONHOME是Python主目录。
PATH是环境变量,操作系统使用它在命令行或终端窗口中查找可执行文件。
[Registry]
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\python;{app}\python\Scripts"
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
ValueType: expandsz; ValueName: "PYTHONPATH"; ValueData: "{app}\python"
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
ValueType: expandsz; ValueName: "PYTHONHOME"; ValueData: "{app}\python"
步骤5.为Apache和PostgreSQL服务创建配置文件
要创建配置文件,我们将使用2个Python脚本,这些脚本将根据用户指定的安装路径生成配置。
这些脚本将在安装程序的[运行]部分中调用。[运行]
部分是可选的,它指定成功安装程序之后但安装程序显示最终对话框之前要运行的任何数量的程序。 接下来,在同一部分中,让我们添加Visual Studio Redistributable Packages的隐藏安装,否则将无法使用Apache和PostgreSQL服务。 create_http_conf.py文件的内容
[Run]
Filename: "{app}\common\VC_redist.x86apache.exe"; Parameters: "/install /passive"; Flags: waituntilterminated
Filename: "{app}\common\vcredist_x86pg.exe"; Parameters: "/install /passive"; Flags: runhidden;
Filename: "{app}\python\python.exe" ;Parameters: "{app}\common\create_http_conf.py"; Flags: runhidden
Filename: "{app}\python\python.exe" ;Parameters: "{app}\common\edit_pg_conf.py"; Flags: runhidden
Filename: "{app}\common\install.bat";Flags: runhidden
Filename: "{app}\common\services_start.bat"; Flags: runhidden
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import sys, os
base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
base_path_un = base_path.replace('\\', '/')
apache_conf_path = os.path.join(base_path, 'Apache24', 'conf', 'extra', 'httpd-wsgi.conf')
print('base_path=',base_path)
CONF = """
LoadFile "%(base)s/python/python39.dll"
LoadModule wsgi_module "%(base)s/python/lib/site-packages/mod_wsgi/server/mod_wsgi.cp39-win32.pyd"
WSGIPythonHome "%(base)s/python"
Alias /static "%(base)s/app/static"
Alias /media "%(base)s/app/media"
<Directory "%(base)s/app/static">
# for Apache 2.4
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
<Directory "%(base)s/app/media">
# for Apache 2.4
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
WSGIScriptAlias / "%(base)s/app/conf/wsgi_prod.py"
WSGIPythonPath "%(base)s/python/"
<Directory "%(base)s/app/conf/">
<Files wsgi_prod.py>
Require all granted
</Files>
</Directory>
"""
conf_content = CONF % {'base': base_path_un}
with open(apache_conf_path, 'w') as fp:
fp.write(conf_content)
# Read in the file
apache_main = os.path.join(base_path, 'Apache24', 'conf', 'httpd.conf')
with open(apache_main, 'r') as file :
filedata = file.read()
# Replace the target string
replace_pattern = 'Define SRVROOT "%(base)s/Apache24"' % {'base' : base_path_un}
find_pattern = 'Define SRVROOT "C:/severcart/Apache24"'
filedata = filedata.replace(find_pattern, replace_pattern)
# Write the file out again
with open(apache_main, 'w') as file:
file.write(filedata)
内容edit_pg_conf.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import sys, os
"""
c:/djangostack/postgresql/bin/postgres.exe "-D" "c:\djangostack\postgresql\data"
"""
base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
base_path_un = base_path.replace('\\', '/')
pg_conf_path = os.path.join(base_path, 'postgresql', 'data', 'postmaster.opts')
# Read in the file
pg_conf_path = os.path.join(base_path, 'postgresql', 'data', 'postmaster.opts')
with open(pg_conf_path, 'r') as file :
filedata = file.read()
# Replace the target string
replace_pattern = base_path_un + '/'
find_pattern = "C:/severcart/"
filedata = filedata.replace(find_pattern, replace_pattern)
# Write the file out again
with open(pg_conf_path, 'w') as file:
file.write(filedata)
Install.bat文件 目录services_start.bat文件
@echo off
..\Apache24\bin\httpd.exe -k install -n "Apache" > install.log 2>&1
..\postgresql\bin\pg_ctl.exe register -N "PostgreSQL" -D ..\postgresql\data > install.log 2>&1
@echo off
net start "Apache"
net start "PostgreSQL"
步骤6:创建卸载程序
对于任何安装程序,还必须提供创建卸载程序的可能性。幸运的是,Inno Setup将为我们完成这项工作,除了需要采取一些步骤来清除程序在OS中存在的痕迹之外。
为此,在[UninstallRun]部分中,我们将注册Windows bat脚本的执行,以停止已安装的服务以及将其删除。 bat脚本的内容: 该脚本停止服务,然后从Windows系统服务列表中删除Apache和PostgreSQL服务。
[UninstallRun]
Filename: "{app}\common\remove.bat"; Flags: runhidden
@echo off
SC STOP Apache
SC STOP PostgreSQL
SC DELETE Apache
SC DELETE PostgreSQL
步骤7.签署开发人员的ES安装程序的可执行文件
软件开发人员使用代码签名证书对应用程序和程序进行数字签名,以证明用户上载的文件是真实的并且未被篡改。对于通过不受控制的第三方下载站点分发软件的发布者,这尤其重要。如果主要操作系统在尝试安装的软件未经过受信任的证书颁发机构签名时,将向最终用户显示错误消息。
例如,您可以在此处购买PFX开发人员证书。该证书购买了一年。
与安装程序一起工作的倒数第二个步骤将是Inno Setup程序完成其工作后,自动启动signtool.exe程序以exe格式签署完成的安装程序。SignTool是一个命令行程序,用于对文件进行数字签名,验证文件签名和文件时间戳。默认情况下,Windows发行版中不包含signtool.exe程序,因此我们下载并安装Windows 10 SDK。
安装完成后,您将在目录中找到signtool.exe:
- x86-> c:\ Program Files(x86)\ Windows Kits \ 10 \ bin \ x86 \
- x64-> c:\ Program Files(x86)\ Windows Kits \ 10 \ bin \ x64 \
对于想了解更多细节的签名程序的人,请访问开发人员的官方网站。它列出了所有命令行选项和用法示例。让我们继续前进。
接下来,让我们设置文件的自动签名。从“工具”菜单中选择“配置签名工具...”。 接下来,单击“添加”按钮。 为工具命名。这是在安装程序脚本中引用该工具时要使用的名称。我将其命名为我的signtool,因为我正在使用signtool.exe。 从命令行粘贴用于签署可执行文件的文本。用$ f替换要签名的文件的名称。 Inno Setup会将$ f变量替换为签名文件。
“C:\ Program Files文件(x86)的\的Windows套件\ 10 \ BIN \ 86 \ signtool.exe”号/ F “C:\ MY_CODE_SIGNING.PFX”/吨timestamp.comodoca.com/authenticode / P MY_PASSWORD $ F
按下后好的,您已经完成了签名工具的配置。
让我们将以下脚本添加到[Setup]部分,以使用我们刚刚配置的签名工具。假设您将工具命名为signtool。
SignTool=signtool
步骤8.组装安装程序
最终的InnoSetup安装程序文件
#define MyAppName «Severcart»
#define MyAppVersion «1.21.0»
#define MyAppPublisher «Severcart Inc.»
#define MyAppURL «www.severcart.ru»
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
SignTool=signtool
AppId={{2CF113D5-B49D-47EF-B85F-AE06EB0E78EB}}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName=c:\severcart
DefaultGroupName={#MyAppName}
OutputBaseFilename=setup
Compression=lzma
SolidCompression=yes
ChangesEnvironment=yes
; Uninstall options
Uninstallable=yes
CreateUninstallRegKey=yes
;WizardSmallImageFile=logo3.bmp
[Icons]
Name: "{userdesktop}\severcart"; Filename: «127.0.0.1:8080/»
[Languages]
Name: «russian»; MessagesFile: «compiler:Languages\Russian.isl»
[Files]
Source: «C:\severcart\*»; Excludes: "*.pyc"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[Registry]
Root: HKLM; Subkey: «SYSTEM\CurrentControlSet\Control\Session Manager\Environment»; \
ValueType: expandsz; ValueName: «Path»; ValueData: "{olddata};{app}\python;{app}\python\Scripts"
Root: HKLM; Subkey: «SYSTEM\CurrentControlSet\Control\Session Manager\Environment»; \
ValueType: expandsz; ValueName: «PYTHONPATH»; ValueData: "{app}\python"
Root: HKLM; Subkey: «SYSTEM\CurrentControlSet\Control\Session Manager\Environment»; \
ValueType: expandsz; ValueName: «PYTHONHOME»; ValueData: "{app}\python"
[Run]
Filename: "{app}\common\VC_redist.x86apache"; Parameters: "/install /passive"; Flags: waituntilterminated
Filename: "{app}\common\vcredist_x86pg"; Parameters: "/install /passive"; Flags: runhidden;
Filename: "{app}\python\python.exe" ;Parameters: "{app}\common\create_http_conf.py"; Flags: runhidden
Filename: "{app}\python\python.exe" ;Parameters: "{app}\common\edit_pg_conf.py"; Flags: runhidden
Filename: "{app}\common\install.bat";Flags: runhidden
Filename: "{app}\common\services_start.bat"; Flags: runhidden
[UninstallRun]
Filename: "{app}\common\remove.bat"; Flags: runhidden
[Code]
function IsWindowsVersionOrNewer(Major, Minor: Integer): Boolean;
var
Version: TWindowsVersion;
begin
GetWindowsVersionEx(Version);
Result :=
(Version.Major > Major) or
((Version.Major = Major) and (Version.Minor >= Minor));
end;
function IsWindows8OrNewer: Boolean;
begin
Result := IsWindowsVersionOrNewer(6, 2);
end;
function CheckPortOccupied(Port:String):Boolean;
var
ResultCode: Integer;
begin
Exec(ExpandConstant('{cmd}'), '/C netstat -na | findstr'+' /C:":'+Port+' "', '',0,ewWaitUntilTerminated, ResultCode);
if ResultCode <> 1 then
begin
Log('this port('+Port+') is occupied');
Result := True;
end else
begin
Result := False;
end;
end;
function InitializeSetup(): Boolean;
var
port_80_check, port_5432_check: boolean;
begin
if not IsWindows8OrNewer() then begin
MsgBox(' . Windows 2012 Windows 8.0.',mbError,MB_OK);
Abort();
Result := False;
end;
port_80_check := CheckPortOccupied('8080');
if port_80_check then begin
MsgBox(' . TCP 8080 .',mbError,MB_OK);
Abort();
Result := False;
end;
port_5432_check := CheckPortOccupied('5432');
if port_5432_check then begin
MsgBox(' . TCP 5432 .',mbError,MB_OK);
Result := False;
Abort();
end;
Result := True;
end;
#define MyAppVersion «1.21.0»
#define MyAppPublisher «Severcart Inc.»
#define MyAppURL «www.severcart.ru»
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
SignTool=signtool
AppId={{2CF113D5-B49D-47EF-B85F-AE06EB0E78EB}}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName=c:\severcart
DefaultGroupName={#MyAppName}
OutputBaseFilename=setup
Compression=lzma
SolidCompression=yes
ChangesEnvironment=yes
; Uninstall options
Uninstallable=yes
CreateUninstallRegKey=yes
;WizardSmallImageFile=logo3.bmp
[Icons]
Name: "{userdesktop}\severcart"; Filename: «127.0.0.1:8080/»
[Languages]
Name: «russian»; MessagesFile: «compiler:Languages\Russian.isl»
[Files]
Source: «C:\severcart\*»; Excludes: "*.pyc"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[Registry]
Root: HKLM; Subkey: «SYSTEM\CurrentControlSet\Control\Session Manager\Environment»; \
ValueType: expandsz; ValueName: «Path»; ValueData: "{olddata};{app}\python;{app}\python\Scripts"
Root: HKLM; Subkey: «SYSTEM\CurrentControlSet\Control\Session Manager\Environment»; \
ValueType: expandsz; ValueName: «PYTHONPATH»; ValueData: "{app}\python"
Root: HKLM; Subkey: «SYSTEM\CurrentControlSet\Control\Session Manager\Environment»; \
ValueType: expandsz; ValueName: «PYTHONHOME»; ValueData: "{app}\python"
[Run]
Filename: "{app}\common\VC_redist.x86apache"; Parameters: "/install /passive"; Flags: waituntilterminated
Filename: "{app}\common\vcredist_x86pg"; Parameters: "/install /passive"; Flags: runhidden;
Filename: "{app}\python\python.exe" ;Parameters: "{app}\common\create_http_conf.py"; Flags: runhidden
Filename: "{app}\python\python.exe" ;Parameters: "{app}\common\edit_pg_conf.py"; Flags: runhidden
Filename: "{app}\common\install.bat";Flags: runhidden
Filename: "{app}\common\services_start.bat"; Flags: runhidden
[UninstallRun]
Filename: "{app}\common\remove.bat"; Flags: runhidden
[Code]
function IsWindowsVersionOrNewer(Major, Minor: Integer): Boolean;
var
Version: TWindowsVersion;
begin
GetWindowsVersionEx(Version);
Result :=
(Version.Major > Major) or
((Version.Major = Major) and (Version.Minor >= Minor));
end;
function IsWindows8OrNewer: Boolean;
begin
Result := IsWindowsVersionOrNewer(6, 2);
end;
function CheckPortOccupied(Port:String):Boolean;
var
ResultCode: Integer;
begin
Exec(ExpandConstant('{cmd}'), '/C netstat -na | findstr'+' /C:":'+Port+' "', '',0,ewWaitUntilTerminated, ResultCode);
if ResultCode <> 1 then
begin
Log('this port('+Port+') is occupied');
Result := True;
end else
begin
Result := False;
end;
end;
function InitializeSetup(): Boolean;
var
port_80_check, port_5432_check: boolean;
begin
if not IsWindows8OrNewer() then begin
MsgBox(' . Windows 2012 Windows 8.0.',mbError,MB_OK);
Abort();
Result := False;
end;
port_80_check := CheckPortOccupied('8080');
if port_80_check then begin
MsgBox(' . TCP 8080 .',mbError,MB_OK);
Abort();
Result := False;
end;
port_5432_check := CheckPortOccupied('5432');
if port_5432_check then begin
MsgBox(' . TCP 5432 .',mbError,MB_OK);
Result := False;
Abort();
end;
Result := True;
end;
步骤9.检查安装程序的工作
就这样,谢谢您的关注。