From 73c09182f4c5a37228ec211f82136a8416282203 Mon Sep 17 00:00:00 2001 From: ChanaChelem Date: Wed, 1 Sep 2021 10:57:18 +0300 Subject: [PATCH 1/4] windows service all the files --- .github/workflows/create-windows-service.yml | 53 ++++++++ README.md | 14 +++ backend/t2wml-service.py | 122 +++++++++++++++++++ backend/t2wml-service.spec | 73 +++++++++++ backend/t2wml-waitress.py | 10 ++ backend/t2wml-waitress.spec | 46 +++++++ 6 files changed, 318 insertions(+) create mode 100644 .github/workflows/create-windows-service.yml create mode 100644 backend/t2wml-service.py create mode 100644 backend/t2wml-service.spec create mode 100644 backend/t2wml-waitress.py create mode 100644 backend/t2wml-waitress.spec diff --git a/.github/workflows/create-windows-service.yml b/.github/workflows/create-windows-service.yml new file mode 100644 index 00000000..52be16cc --- /dev/null +++ b/.github/workflows/create-windows-service.yml @@ -0,0 +1,53 @@ +name: Create Windows Installer +on: + push: + tags: + - v* + +# For Code Signing please make sure the following secrets are defined in the repository: +# WINDOWS_CERTIFICATE_BASE64 - The Windows code signing certificate, base64 encoded +# WINDOWS_CERTIFICATE_PASSWORD - The password for Windows the certificate + +jobs: + windows: + name: Create the Windows Backend service + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: '3.8' + - name: Set backend environment + working-directory: backend + run: | + pip install --upgrade setuptools pip wheel + pip install t2wml-api + pip install -r requirements.txt + pip install pyinstaller pywin32 semver requests + pip uninstall -y typing typing-extensions + pip install waitress + - name: Create installer + working-directory: backend + env: # Replace unicode characters with ? + PYTHONIOENCODING: :replace + PYTHONLEGACYWINDOWSIOENCODING: true + CI: false # Otherwise React stops on warnings + CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + run: | + pyinstaller --clean --noupx t2wml-service.spec + - name: Get the version + id: get_version + run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} + shell: bash + - name: Upload to Release + id: upload-release-asset + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: backend/dist/t2wml-service.exe + asset_name: t2wml-service-${{ steps.get_version.outputs.VERSION }}.exe + tag: ${{ github.ref }} + overwrite: true + body: "Windows Setup" \ No newline at end of file diff --git a/README.md b/README.md index 3c940d17..3e60f85f 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,20 @@ You can also open the Chrome Developers Tools from the GUI's `Debug` menu. #### Backend Development To develop the backend, you need to launch the `Backend` from the debug menu. You will be able to set breakpoints and debug properly. If you want to run the GUI, start the `Build and Watch GUI` and `t2wml GUI` tasks, as well. +### Windows service +To run the backend as a service on windows: +Download the file windows-service.exe, and run with Administrator privilges: +Install: +`t2wml-service.exe install` +Start: +`t2wml-service.exe start` +Debug: +`t2wml-service.exe debug` +Stop: +`t2wml-service.exe stop` +Uninstall: +`t2wml-service.exe remove` + ## Usage with GUI diff --git a/backend/t2wml-service.py b/backend/t2wml-service.py new file mode 100644 index 00000000..05779c7e --- /dev/null +++ b/backend/t2wml-service.py @@ -0,0 +1,122 @@ +""" +# Prerequisites: +cd backend +virtualenv env-service +(activate virtual env) +pip install -r requirements.txt +pip install --upgrade pyinstaller +pip install pywin32 +pip install waitress +pip install requests +pip install -e ../../t2wml-api +# Build: +pyinstaller --clean --noupx t2wml-service.spec +# With Administrator privilges +# Install: +dist\t2wml-service.exe install +# Start: +dist\t2wml-service.exe start +# Install with autostart: +dist\t2wml-service.exe --startup delayed install +# Debug: +dist\t2wml-service.exe debug +# Stop: +dist\t2wml-service.exe stop +# Uninstall: +dist\t2wml-service.exe remove +""" + +import time +import ctypes +import sys +import win32serviceutil # ServiceFramework and commandline helper +import win32service # Events +import servicemanager # Simple setup and logging +import win32event +from waitress import serve +import socket +import threading + +from application import app + + +def runApp(): + app.run(port=13000, debug=False, host=None, use_reloader=False) + + +class WaitressService(threading.Thread): + + def __init__(self): + threading.Thread.__init__(self) + + def run(self): + print('thread start\n') + serve(runApp(), listen='localhost:*', host='localhost', port=13000) + print('thread done\n') + + def get_id(self): + # returns id of the respective thread + if hasattr(self, '_thread_id'): + return self._thread_id + for id, thread in threading._active.items(): + if thread is self: + return id + + def exit(self): + thread_id = self.get_id() + res = ctypes.pythonapi.PyThreadState_SetAsyncExc( + thread_id, ctypes.py_object(SystemExit)) + if res > 1: + ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0) + print('Exception raise failure') + + +class MyServiceFramework(win32serviceutil.ServiceFramework): + + _svc_name_ = 'WaitressService' + _svc_display_name_ = 'Waitress service' + _svc_description_ = 'Python waitress WSGI service' + + def __init__(self, args): + win32serviceutil.ServiceFramework.__init__(self, args) + self.stopEvt = win32event.CreateEvent(None, 0, 0, None) + socket.setdefaulttimeout(60) + + def SvcStop(self): + """Stop the service""" + servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, + servicemanager.PYS_SERVICE_STOPPED, + (self._svc_name_, '')) + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + win32event.SetEvent(self.stopEvt) + + def SvcDoRun(self): + """Start the service; does not return until stopped""" + print('main start') + servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, + servicemanager.PYS_SERVICE_STARTED, + (self._svc_name_, '')) + self.service_impl = WaitressService() + self.ReportServiceStatus(win32service.SERVICE_RUNNING) + # Run the service + self.service_impl.start() + + print('waiting on win32event') + win32event.WaitForSingleObject(self.stopEvt, win32event.INFINITE) + self.service_impl.exit() # raise SystemExit in inner thread + print('waiting on thread') + self.service_impl.join() + print('main done') + + +def init(): + if len(sys.argv) == 1: + servicemanager.Initialize() + servicemanager.PrepareToHostSingle(MyServiceFramework) + servicemanager.StartServiceCtrlDispatcher() + else: + win32serviceutil.HandleCommandLine(MyServiceFramework) + + +if __name__ == '__main__': + init() \ No newline at end of file diff --git a/backend/t2wml-service.spec b/backend/t2wml-service.spec new file mode 100644 index 00000000..5d114fd7 --- /dev/null +++ b/backend/t2wml-service.spec @@ -0,0 +1,73 @@ +# -*- mode: python ; coding: utf-8 -*- + +""" +# Prerequisites: +cd backend +virtualenv env-service +(activate virtual env) +pip install -r requirements.txt +pip install --upgrade pyinstaller +pip install pywin32 +pip install waitress +pip install requests +pip install t2wml-api +# Build: +pyinstaller --clean --noupx t2wml-service.spec +# With Administrator privilges +# Install: +dist\t2wml-service.exe install +# Start: +dist\t2wml-service.exe start +# Debug: +dist\t2wml-service.exe debug +# Stop: +dist\t2wml-service.exe stop +# Uninstall: +dist\t2wml-service.exe remove +""" + +import sys +from os import path, getcwd +site_packages = next(p for p in sys.path if 'site-packages' in p) + +def copy_package(name): + return [(path.join(site_packages, name), name)] + +block_cipher = None + + +a = Analysis(['t2wml-service.py'], + pathex=[getcwd()], + binaries=[], + datas=copy_package('distributed'), + hiddenimports=['win32timezone', 'pandas','pandas._libs.tslibs.base', 'rltk', 'logging.config', 'cmath'], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) + +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='t2wml-service', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir='.', + console=True, + disable_windowed_traceback=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None + ) \ No newline at end of file diff --git a/backend/t2wml-waitress.py b/backend/t2wml-waitress.py new file mode 100644 index 00000000..9fe52ee0 --- /dev/null +++ b/backend/t2wml-waitress.py @@ -0,0 +1,10 @@ +from waitress import serve + +from application import app + +def run(): + app.run(port=13000, debug=False, host=None, use_reloader=False) + +serve(run()) + +# serve(wsgiapp, host='localhost', port=13000) \ No newline at end of file diff --git a/backend/t2wml-waitress.spec b/backend/t2wml-waitress.spec new file mode 100644 index 00000000..a713e40c --- /dev/null +++ b/backend/t2wml-waitress.spec @@ -0,0 +1,46 @@ +# -*- mode: python ; coding: utf-8 -*- +# Pyinstaller spec file. + +# pip install --upgrade pyinstaller +# pyinstaller --clean --noupx .\t2wml-waitress.spec + +from PyInstaller.utils.hooks import copy_metadata +import sys +from os import path, getcwd +site_packages = next(p for p in sys.path if 'site-packages' in p) + +def copy_package(name): + return [(path.join(site_packages, name), name)] + +block_cipher = None + + +a = Analysis(['t2wml-waitress.py'], + pathex=[getcwd()], + binaries=[], + datas=copy_package('distributed'), + hiddenimports=['pandas','pandas._libs.tslibs.base', 'rltk', 'logging.config', 'cmath'], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='t2wml-waitress', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True ) \ No newline at end of file From df73036feedc7731b425214608e09c88632ad8fc Mon Sep 17 00:00:00 2001 From: Itay Zandbank Date: Wed, 1 Sep 2021 11:59:53 +0300 Subject: [PATCH 2/4] Use waitress more properly --- .github/workflows/create-windows-service.yml | 6 +++--- backend/t2wml-service.py | 20 +++++++------------- backend/t2wml-waitress.py | 10 +++++----- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/.github/workflows/create-windows-service.yml b/.github/workflows/create-windows-service.yml index 52be16cc..72f7deee 100644 --- a/.github/workflows/create-windows-service.yml +++ b/.github/workflows/create-windows-service.yml @@ -1,4 +1,4 @@ -name: Create Windows Installer +name: Create Windows Service on: push: tags: @@ -10,7 +10,7 @@ on: jobs: windows: - name: Create the Windows Backend service + name: Create the Windows Service runs-on: windows-latest steps: - name: Checkout @@ -50,4 +50,4 @@ jobs: asset_name: t2wml-service-${{ steps.get_version.outputs.VERSION }}.exe tag: ${{ github.ref }} overwrite: true - body: "Windows Setup" \ No newline at end of file + body: "Windows Setup" diff --git a/backend/t2wml-service.py b/backend/t2wml-service.py index 05779c7e..340479fc 100644 --- a/backend/t2wml-service.py +++ b/backend/t2wml-service.py @@ -37,21 +37,15 @@ import socket import threading -from application import app - - -def runApp(): - app.run(port=13000, debug=False, host=None, use_reloader=False) - - +from causx_application import app class WaitressService(threading.Thread): - + def __init__(self): threading.Thread.__init__(self) def run(self): print('thread start\n') - serve(runApp(), listen='localhost:*', host='localhost', port=13000) + serve(app, host='localhost', port=13000, trusted_proxy='localhost') print('thread done\n') def get_id(self): @@ -73,9 +67,9 @@ def exit(self): class MyServiceFramework(win32serviceutil.ServiceFramework): - _svc_name_ = 'WaitressService' - _svc_display_name_ = 'Waitress service' - _svc_description_ = 'Python waitress WSGI service' + _svc_name_ = 't2wml_backend' + _svc_display_name_ = 'T2WML Backend Service' + _svc_description_ = 'T2WML Backend Service for Causx' def __init__(self, args): win32serviceutil.ServiceFramework.__init__(self, args) @@ -119,4 +113,4 @@ def init(): if __name__ == '__main__': - init() \ No newline at end of file + init() diff --git a/backend/t2wml-waitress.py b/backend/t2wml-waitress.py index 9fe52ee0..d51136d5 100644 --- a/backend/t2wml-waitress.py +++ b/backend/t2wml-waitress.py @@ -1,10 +1,10 @@ from waitress import serve -from application import app +from causx_application import app -def run(): - app.run(port=13000, debug=False, host=None, use_reloader=False) +#def run(): +# app.run(port=13000, debug=False, host=None, use_reloader=False) -serve(run()) +serve(app, port=13000, host='localhost') -# serve(wsgiapp, host='localhost', port=13000) \ No newline at end of file +# serve(wsgiapp, host='localhost', port=13000) From 9250f3726511aebef5d7bc2c66b516dfe08d3037 Mon Sep 17 00:00:00 2001 From: Itay Zandbank Date: Wed, 1 Sep 2021 13:20:53 +0300 Subject: [PATCH 3/4] Add is-alive to Causx and service --- .github/workflows/create-windows-service.yml | 1 + backend/causx_application.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/create-windows-service.yml b/.github/workflows/create-windows-service.yml index 72f7deee..b076db1f 100644 --- a/.github/workflows/create-windows-service.yml +++ b/.github/workflows/create-windows-service.yml @@ -35,6 +35,7 @@ jobs: PYTHONLEGACYWINDOWSIOENCODING: true CI: false # Otherwise React stops on warnings CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + run: | pyinstaller --clean --noupx t2wml-service.spec - name: Get the version diff --git a/backend/causx_application.py b/backend/causx_application.py index 70a26d21..b068a154 100644 --- a/backend/causx_application.py +++ b/backend/causx_application.py @@ -187,6 +187,15 @@ def get_mapping(preload=False): get_layers(response, calc_params) return response, 200 + +@app.route('/api/is-alive') +def is_alive1(): + return 'Causx Backend is here', 200 + +@app.route('/api/causx/is-alive') +def is_alive2(): + return 'Causx Backend is here', 200 + @app.route('/api/causx/token', methods=['GET']) def get_token(): return {"token": encode_auth_token()}, 200 From 7379073264ad6e6222e910142f8e12fd6e669717 Mon Sep 17 00:00:00 2001 From: Itay Zandbank Date: Wed, 1 Sep 2021 13:21:39 +0300 Subject: [PATCH 4/4] Undo redundant change --- .github/workflows/create-windows-service.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create-windows-service.yml b/.github/workflows/create-windows-service.yml index b076db1f..2c38f076 100644 --- a/.github/workflows/create-windows-service.yml +++ b/.github/workflows/create-windows-service.yml @@ -35,7 +35,6 @@ jobs: PYTHONLEGACYWINDOWSIOENCODING: true CI: false # Otherwise React stops on warnings CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} - run: | pyinstaller --clean --noupx t2wml-service.spec - name: Get the version @@ -52,3 +51,4 @@ jobs: tag: ${{ github.ref }} overwrite: true body: "Windows Setup" +