Создаём установщик веб-приложения Python, включающий Apache, Django и PostgreSQL для ОС Windows



    Данный пост является продолжением первой части статьи на Хабре, где было подробно рассказано о развертывании Django стека на MS Windows. Далее будет представлена пошаговая инструкция по созданию инсталлятора, который будет автоматизировать процесс установки стека на других компьютерах без необходимости работы в командной строке, созданием виртуальных машин и т.д., где вся последовательность действий будет сводится к действиям Далее -> Далее -> Готово.

    Итак, что должен делать инсталлятор:

    1. Распаковать все необходимые программы и компоненты в указанную пользователем директорию.
    2. Выполнить проверки перед установкой.
    3. Прописать интерпретатор Python в реестре Windows.
    4. Установить, если ещё не установлены, программные библиотеки зависимостей.
    5. Создать службы Apache и PostgreSQL, затем стартовать их.
    6. Дополнительным плюсом будет автоматическое создание программы деинсталлятора, который удалит установленный стек, если пользователь этого захочет.

    Среди возможных вариантов установщиков выберем бесплатный установщик 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] установочного файла. Раздел [Code] – это необязательный раздел, определяющий сценарий Pascal. Сценарий Pascal можно использовать для настройки установки или удаления разными способами. Обратите внимание, что создать сценарий Pascal непросто и требует опыта работы с Inno Setup и умений программирования на Pascal или, по крайней мере, на аналогичном языке программирования.

    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;

    Для проверки доступности TCP портов создадим следующую функцию:

    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;

    Вызывать проверочные функции будем в функции InitializeSetup, вызываемой во время инициализации установки. Возвращает False, для отмены установки, в противном случае — True.

    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. Прописываем Python в реестре Windows


    В этом необязательном разделе определяются любые ключи / значения реестра, которые программа установки должна создать или изменить в системе пользователя.

    Для этого добавляем ключи 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 скриптами, которые сгенерируют конфигурационные на основе заданного пользователем пути установки.

    Вызов скриптов будет производиться в разделе [Run] установщика.

    Раздел [Run] является необязательным и указывает любое количество программ, которые необходимо выполнить после успешной установки программы, но до того, как программа установки отобразит последнее диалоговое окно.

    Далее в эту же секцию добавим скрытую установку распространяемые пакеты Visual Studio без которых службы Apache и PostgreSQL работать не будут.

    [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

    Содержимое файла create_http_conf.py

    #!/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

    @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

    Содержимое файла services_start.bat

    @echo off

    net start "Apache"

    net start "PostgreSQL"

    Шаг 6: Создаем деинсталлятор


    Для любого инсталлятора также необходимо предусмотреть возможность создания программы деинсталлятора. К счастью программа Inno Setup сделает эту работу за нас, за исключением некоторых действий, которые нужно предусмотреть для очистки следов присутствия программы в ОС.

    Для этого в секции [UninstallRun] пропишем выполнение bat скрипта Windows для остановки установленных служб, а также их удаления.

    [UninstallRun]
    Filename: "{app}\common\remove.bat"; Flags: runhidden

    Содержимое bat скрипта:

    @echo off

    SC STOP Apache
    SC STOP PostgreSQL

    SC DELETE Apache
    SC DELETE PostgreSQL

    Скрипт выполняет остановку служб, затем удаляет службы Apache и PostgreSQL из перечня системных служб Windows.

    Шаг 7. Подписание исполняемого файла инсталлятора ЭП разработчика


    Сертификаты подписи кода используются разработчиками программного обеспечения для цифровой подписи приложений и программ, чтобы доказать, что файл, загружаемый пользователем, является подлинным и не был взломан. Это особенно важно для издателей, которые распространяют свое программное обеспечение через сторонние сайты загрузки, которые они не могут контролировать. Основные операционные системы будут показывать конечным пользователям сообщение об ошибке, если программное обеспечение, которое они пытаются установить, не подписано доверенным центром сертификации.

    Купить сертификат разработчика PFX, например можно здесь. Сертификат приобретается на год.

    Предпоследним шагом над работы с инсталлятором будет автоматический запуск программы signtool.exe для подписания готового инсталлятора в формате exe после того как программа Inno Setup завершит свою работу. 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\

    Для тех, кто хочет познакомиться с программой подписания подробнее посетите официальный сайт разработчика. В нем перечислены все опции командной строки и примеры использования. Двигаемся далее.

    Далее настроим автоматическое подписание файла. Выбираем «Configure Sign Tools...» из меню «Tools».



    Далее нажимаем на кнопку «Add»



    Дадим инструменту имя. Это имя, которое вы будете использовать при обращении к инструменту в сценариях установщика. Я назвал свой signtool, потому что использую signtool.exe.



    Вставьте текст, который вы используете для подписи исполняемых файлов из командной строки. Замените имя подписываемого файла на $f. Inno Setup заменит переменную $f подписываемым файлом.

    «C:\Program Files (x86)\Windows Kits\10\bin\x86\signtool.exe» sign /f «C:\MY_CODE_SIGNING.PFX» /t timestamp.comodoca.com/authenticode /p MY_PASSWORD $f



    После нажатия OK вы закончите настройку инструмента подписи.



    Добавим следующий сценарий в раздел [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;





    Шаг 9. Проверяем работу инсталлятора




















    На это всё, спасибо за внимание.

    Похожие публикации

    Средняя зарплата в IT

    110 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 8 431 анкеты, за 2-ое пол. 2020 года Узнать свою зарплату
    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

    Комментарии 5

      +1
      Классная статья.

      1. Как вы разворачиваете Python, базу, Apache? Неплохо бы эту тему тоже развернуть. В первой части написано, но там всё руками, а как это всё из инсталятора делается?
      2. Вы проверяете наличие открытого порта для базы, если он открыт то установка не произойдёт. Т.е. если в системе уже установлен PostgreSQL, его надо сносить?
      3. Исходя из пункта 2, обновление тоже не предусмотрено, всё сносим и устанавливаем с нуля?
      4. Очень интересен механизм подписи. Он автоматом подписывает все исполняемые файлы и динамические библиотеки, входящие в состав дистрибутива, или только сам дистрибутив?
        0
        Горшочек — не вари.

        Сначала подумал зачем все это, а когда посетил вышеуказаный сайт в обход антивируса (в «темном» листе у eset ) оказалось что это реклама ПО для учета картриджей.

        Раньше думал что такую рекламу пропускают только как корпоративные блоги.
          0
          Помню, тыкал данную программу.

          Хотелось бы уточнить. Как программа, которая раньше была под GPL v2 [1], стала платной [2]?

          И вытекающий от сюда вопрос: стоит ли доверять разработчику, который мечется между лицензиями?

          [1]
          www.linux.org.ru/news/linux-general/13212673
          Состоялся релиз Severcart 0.8.1. Приложение применяется для отслеживания перемещений и учёта расходных материалов для печатающего оборудования.
          Разработка ведётся на языке Python 3 с применением фреймворка Django и СУБД PostgreSQL. Исходный код распространяется под лицензией GPLv2.

          www.opennet.ru/opennews/art.shtml?num=46022
          Код программы распространяется под лицензией GPLv2.
          github.com/sfcl/severcart <<< 404

          [2]
          www.severcart.ru/comp

            0
            А что мешает разработчику продавать продукт, с лицензией gplv2 или выпускать под разными лицензиями разные версии? Вон Red Hat и много кто ещё этим занимаются, стандартная практика
            –1
            почему нет тегов «ненормальное программирование», «кодобред»? Это всё же just for lulz я так понимаю сделано? Или всё серьёзно?

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое