mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-09-22 23:08:08 +03:00
373 lines
10 KiB
Python
373 lines
10 KiB
Python
# Copyright (C) 2024 Loren Eteval <loren.eteval@proton.me>
|
|
#
|
|
# This file is part of Furious.
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
from __future__ import annotations
|
|
|
|
from Furious.Utility import *
|
|
|
|
import os
|
|
import sys
|
|
import shutil
|
|
import logging
|
|
import argparse
|
|
import subprocess
|
|
|
|
logging.basicConfig(
|
|
format='[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s',
|
|
level=logging.INFO,
|
|
)
|
|
logging.raiseExceptions = False
|
|
|
|
logger = logging.getLogger('Deploy')
|
|
|
|
# Exit status
|
|
EXIT_SUCCESS = 0
|
|
EXIT_FAILURE = 1
|
|
|
|
DEPLOY_DIR_NAME = f'{APPLICATION_NAME}-Deploy'
|
|
|
|
if PLATFORM == 'Windows':
|
|
NUITKA_BUILD = (
|
|
f'python -m nuitka '
|
|
f'--standalone --plugin-enable=pyside6 '
|
|
f'--disable-console '
|
|
f'--assume-yes-for-downloads '
|
|
f'--include-package-data=Furious '
|
|
f'--windows-icon-from-ico=\"Icons/png/rocket-takeoff-window.png\" '
|
|
f'--force-stdout-spec=^%TEMP^%/_Furious_Enable_Stdout '
|
|
f'--force-stderr-spec=^%TEMP^%/_Furious_Enable_Stderr '
|
|
f'Furious '
|
|
f'--output-dir=\"{ROOT_DIR / DEPLOY_DIR_NAME}\"'
|
|
)
|
|
elif PLATFORM == 'Darwin':
|
|
NUITKA_BUILD = (
|
|
f'python -m nuitka '
|
|
f'--standalone --plugin-enable=pyside6 '
|
|
f'--disable-console '
|
|
f'--assume-yes-for-downloads '
|
|
f'--include-package-data=Furious '
|
|
f'--macos-create-app-bundle '
|
|
f'--macos-app-icon=\"Icons/png/rocket-takeoff-window.png\" '
|
|
f'--macos-app-name=\"Furious\" '
|
|
f'Furious-GUI.py '
|
|
f'--output-dir=\"{ROOT_DIR / DEPLOY_DIR_NAME}\"'
|
|
)
|
|
else:
|
|
# Deploy: Not implemented
|
|
NUITKA_BUILD = ''
|
|
|
|
if PLATFORM == 'Windows':
|
|
if PLATFORM_RELEASE.endswith('Server'):
|
|
# Windows server. Fixed to windows10
|
|
winVerCompatible = f'{PLATFORM.lower()}10'
|
|
else:
|
|
winVerCompatible = f'{PLATFORM.lower()}{PLATFORM_RELEASE}'
|
|
|
|
ARTIFACT_NAME = (
|
|
f'{APPLICATION_NAME}-{APPLICATION_VERSION}-'
|
|
f'{winVerCompatible}-{PLATFORM_MACHINE.lower()}'
|
|
)
|
|
|
|
WIN_UNZIPPED = f'{APPLICATION_NAME}-{APPLICATION_VERSION}-{winVerCompatible}'
|
|
elif PLATFORM == 'Darwin':
|
|
value = versionToValue(PYSIDE6_VERSION)
|
|
|
|
# https://doc.qt.io/qt-6/supported-platforms.html
|
|
if value <= versionToValue('6.4.3'):
|
|
macVerCompatible = 'macOS-10.9'
|
|
elif value <= versionToValue('6.7.3'):
|
|
macVerCompatible = 'macos-11.0'
|
|
else:
|
|
macVerCompatible = 'macOS-12.0'
|
|
|
|
ARTIFACT_NAME = (
|
|
f'{APPLICATION_NAME}-{APPLICATION_VERSION}-'
|
|
f'{macVerCompatible}-{PLATFORM_MACHINE.lower()}'
|
|
)
|
|
|
|
MAC_APP_DIR = ROOT_DIR / 'app'
|
|
MAC_DMG_FILENAME = f'{ARTIFACT_NAME}.dmg'
|
|
MAC_CREATE_DMG_CMD = (
|
|
f'create-dmg '
|
|
f'--volname \"Furious\" '
|
|
f'--volicon \"Icons/png/rocket-takeoff-window.png\" '
|
|
f'--window-pos 200 120 '
|
|
f'--window-size 600 300 '
|
|
f'--icon-size 100 '
|
|
f'--icon \"Furious-GUI.app\" 175 120 '
|
|
f'--hide-extension \"Furious-GUI.app\" '
|
|
f'--app-drop-link 425 120 '
|
|
f'\"{ROOT_DIR / MAC_DMG_FILENAME}\" '
|
|
f'\"{MAC_APP_DIR}\"'
|
|
)
|
|
else:
|
|
# Deploy: Not implemented
|
|
ARTIFACT_NAME = ''
|
|
|
|
|
|
def downloadXrayAssets(url, filename):
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
raise ModuleNotFoundError('missing requests module')
|
|
|
|
try:
|
|
# Make sure the save directory exists
|
|
if not os.path.exists(XRAY_ASSET_DIR):
|
|
os.makedirs(XRAY_ASSET_DIR)
|
|
|
|
# Full path where the file will be saved
|
|
filepath = os.path.join(XRAY_ASSET_DIR, filename)
|
|
|
|
# Send an HTTP GET request to the URL
|
|
response = requests.get(url)
|
|
response.raise_for_status() # Check if the request was successful
|
|
|
|
# Open the save_path in write-binary mode and write the content of the response
|
|
with open(filepath, 'wb') as file:
|
|
file.write(response.content)
|
|
|
|
except Exception as ex:
|
|
# Any non-exit exceptions
|
|
|
|
logger.error(
|
|
f'failed to download file from {url}. Status code: {response.status_code}'
|
|
)
|
|
|
|
return False
|
|
else:
|
|
logger.info(f'file downloaded successfully and saved to {filepath}')
|
|
|
|
return True
|
|
|
|
|
|
def cleanup():
|
|
try:
|
|
shutil.rmtree(ROOT_DIR / DEPLOY_DIR_NAME)
|
|
except Exception as ex:
|
|
# Any non-exit exceptions
|
|
|
|
logger.error(f'remove deployment dir failed: {ex}')
|
|
else:
|
|
logger.info(f'remove deployment dir success')
|
|
|
|
if PLATFORM == 'Windows':
|
|
# More cleanup on Windows
|
|
try:
|
|
# Remove artifact
|
|
os.remove(ROOT_DIR / f'{ARTIFACT_NAME}.zip')
|
|
except Exception as ex:
|
|
# Any non-exit exceptions
|
|
|
|
logger.error(f'remove artifact failed: {ex}')
|
|
else:
|
|
logger.info(f'remove artifact success')
|
|
|
|
try:
|
|
# Remove unzipped folder
|
|
shutil.rmtree(ROOT_DIR / WIN_UNZIPPED)
|
|
except Exception as ex:
|
|
# Any non-exit exceptions
|
|
|
|
logger.error(f'remove unzipped dir failed: {ex}')
|
|
else:
|
|
logger.info('remove unzipped dir success')
|
|
elif PLATFORM == 'Darwin':
|
|
# More cleanup on Darwin
|
|
try:
|
|
# Remove artifact
|
|
os.remove(ROOT_DIR / f'{ARTIFACT_NAME}.dmg')
|
|
except Exception as ex:
|
|
# Any non-exit exceptions
|
|
|
|
logger.error(f'remove artifact failed: {ex}')
|
|
else:
|
|
logger.info(f'remove artifact success')
|
|
|
|
try:
|
|
# Remove app folder
|
|
shutil.rmtree(MAC_APP_DIR)
|
|
except Exception as ex:
|
|
# Any non-exit exceptions
|
|
|
|
logger.error(f'remove app dir failed: {ex}')
|
|
else:
|
|
logger.info(f'remove app dir success')
|
|
else:
|
|
# Deploy: Not implemented
|
|
pass
|
|
|
|
|
|
def download():
|
|
# URLs of geosite and geoip assets
|
|
url_geosite = 'https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat'
|
|
url_geoip = 'https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat'
|
|
|
|
# Xray assets names
|
|
filename_geosite = 'geosite.dat'
|
|
filename_geoip = 'geoip.dat'
|
|
|
|
return all(
|
|
[
|
|
downloadXrayAssets(url_geosite, filename_geosite),
|
|
downloadXrayAssets(url_geoip, filename_geoip),
|
|
]
|
|
)
|
|
|
|
|
|
def printStandardStream(stdout, stderr):
|
|
if isinstance(stdout, bytes):
|
|
decoded_stdout = stdout.decode('utf-8', 'replace')
|
|
else:
|
|
decoded_stdout = stdout
|
|
|
|
if isinstance(stderr, bytes):
|
|
decoded_stderr = stderr.decode('utf-8', 'replace')
|
|
else:
|
|
decoded_stderr = stderr
|
|
|
|
print(f'stdout:\n{decoded_stdout}stderr:\n{decoded_stderr}', flush=True)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
'-d',
|
|
'--download',
|
|
action='store_true',
|
|
help='Download latest asset files',
|
|
)
|
|
parser.add_argument(
|
|
'-c',
|
|
'--cleanup',
|
|
action='store_true',
|
|
help='Cleanup deployment files',
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.cleanup:
|
|
cleanup()
|
|
|
|
logger.info('cleanup done')
|
|
|
|
sys.exit(EXIT_SUCCESS)
|
|
|
|
if args.download:
|
|
success = EXIT_SUCCESS if download() else EXIT_FAILURE
|
|
|
|
sys.exit(success)
|
|
|
|
try:
|
|
import nuitka
|
|
except ImportError:
|
|
raise ModuleNotFoundError('please install nuitka to run this script')
|
|
|
|
logger.info('building')
|
|
|
|
try:
|
|
result = runExternalCommand(
|
|
NUITKA_BUILD,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
shell=True,
|
|
check=True,
|
|
)
|
|
except subprocess.CalledProcessError as err:
|
|
logger.error(f'build failed with returncode {err.returncode}')
|
|
|
|
printStandardStream(err.stdout, err.stderr)
|
|
|
|
sys.exit(EXIT_FAILURE)
|
|
else:
|
|
logger.info(f'build success')
|
|
|
|
printStandardStream(result.stdout, result.stderr)
|
|
|
|
if PLATFORM == 'Windows':
|
|
try:
|
|
shutil.rmtree(ROOT_DIR / DEPLOY_DIR_NAME / WIN_UNZIPPED)
|
|
except FileNotFoundError:
|
|
pass
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
raise
|
|
|
|
shutil.copytree(
|
|
ROOT_DIR / DEPLOY_DIR_NAME / f'{APPLICATION_NAME}.dist',
|
|
ROOT_DIR / DEPLOY_DIR_NAME / WIN_UNZIPPED,
|
|
)
|
|
shutil.make_archive(
|
|
ARTIFACT_NAME,
|
|
'zip',
|
|
ROOT_DIR / DEPLOY_DIR_NAME,
|
|
WIN_UNZIPPED,
|
|
logger=logger,
|
|
)
|
|
elif PLATFORM == 'Darwin':
|
|
try:
|
|
shutil.rmtree(MAC_APP_DIR)
|
|
except FileNotFoundError:
|
|
pass
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
raise
|
|
|
|
try:
|
|
os.mkdir(MAC_APP_DIR)
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
raise
|
|
|
|
shutil.copytree(
|
|
ROOT_DIR / DEPLOY_DIR_NAME / 'Furious-GUI.app',
|
|
MAC_APP_DIR / 'Furious-GUI.app',
|
|
)
|
|
|
|
logger.info('generating dmg')
|
|
|
|
try:
|
|
result = runExternalCommand(
|
|
MAC_CREATE_DMG_CMD,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
shell=True,
|
|
check=True,
|
|
)
|
|
except subprocess.CalledProcessError as err:
|
|
logger.error(f'generate dmg failed with returncode {err.returncode}')
|
|
|
|
printStandardStream(err.stdout, err.stderr)
|
|
|
|
sys.exit(EXIT_FAILURE)
|
|
else:
|
|
logger.info(f'generate dmg success: {MAC_DMG_FILENAME}')
|
|
|
|
printStandardStream(result.stdout, result.stderr)
|
|
else:
|
|
# Deploy: Not implemented
|
|
pass
|
|
|
|
sys.exit(EXIT_SUCCESS)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|