diff --git a/.gitignore b/.gitignore index fbf2a5ac..4282723c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build-packages .local -vendor/ \ No newline at end of file +vendor/ +.vscode/ \ No newline at end of file diff --git a/build-install-local.sh b/build-install-local.sh deleted file mode 100755 index a50d3ba7..00000000 --- a/build-install-local.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -LOCATION="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -# build an app-only package (ie don't include images) and store the package path -echo "building app-only package" -package=$(${LOCATION}/package --app | awk -F'saved package: ' '/^saved package: / {print $2}') -echo "generated package: {package}" - -# use a temp folder for unpacking and make sure to clean up when done -tmp="/tmp/build-install-local-$(date +%s)" -echo "prepping temp folder: {tmp}" -rm -rf $tmp -mkdir -p $tmp -trap 'rm -rf $tmp' EXIT - -# unpack to temp folder -echo "unpacking app package: $package" -tar xf $package -C $tmp - -# install package -echo "installing package" -${tmp}/*/install.sh - -echo "done." diff --git a/config.json b/config.json index 08fad59a..bd88e6c7 100644 --- a/config.json +++ b/config.json @@ -1,7 +1,7 @@ { "name": "streamline-emr", "org": "streamline", - "version": "3.0", + "version": "3.4", "platforms": [ "linux/arm64" ] diff --git a/docker/Dockerfile.streamline-emr.arm64 b/docker/Dockerfile.streamline-emr.arm64 index 53c93670..eca683ac 100644 --- a/docker/Dockerfile.streamline-emr.arm64 +++ b/docker/Dockerfile.streamline-emr.arm64 @@ -1 +1 @@ -FROM streamlinehealth/streamline:signalytic \ No newline at end of file +FROM streamline:signalytic_latest \ No newline at end of file diff --git a/hosted-app-utils b/hosted-app-utils index 6af4c4ae..98c3a9ba 160000 --- a/hosted-app-utils +++ b/hosted-app-utils @@ -1 +1 @@ -Subproject commit 6af4c4ae8520e499c53a6bd80567454061e89103 +Subproject commit 98c3a9ba36a477e00c34eeb838674b70132659df diff --git a/install.sh b/install.sh deleted file mode 100755 index bc754000..00000000 --- a/install.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -# -# Copyright (C) Wendepunkt Medical Innovations, Inc - All Rights Reserved. -# Unauthorized copying of this file, via any medium is strictly prohibited. -# Proprietary and confidential -# -# For information: dev@signalytic.ca -# - -# -# Install the streamline-emr application -# -# The caller of this script must check the exit value and possibly do a -# roll-back if it is non-zero. If the exit value is non-zero services have not -# been restarted. -# -# Note: This version resets data from v1 installations - -LOCATION="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -if [ "$EUID" -ne 0 ] - then echo "Please run as root" - exit 1 -fi - -set -e - -APP_SLUG="streamline_emr" -ORG_SLUG="streamline" -INSTALL_ROOT="/var/signalytic/clientapps/${ORG_SLUG}/${APP_SLUG}" -VERSION_FILE="${INSTALL_ROOT}/version" -DATA_ROOT="${INSTALL_ROOT}/data" - -# stop the application (if installed) -if [ -f ${INSTALL_ROOT}/stop ]; then - ${INSTALL_ROOT}/stop -fi - -# reset data for any v1 or v2.0 installations -if grep -q "^1." ${VERSION_FILE} || grep -q "^2.0" ${VERSION_FILE}; then - echo "demo installation detected (v$(cat ${VERSION_FILE})) - resetting data" - rm -rf ${DATA_ROOT} -fi - -# copy root files (if present) -if [ -d "${LOCATION}/root" ] && [ -n "$(ls -A ${LOCATION}/root)" ]; then - echo "importing root files" - cp -rfv $LOCATION/root/* / -fi - -# import docker images (if present) -for f in $(find $LOCATION/images -name "*.img" 2>/dev/null);do - echo "importing docker image: $f" - docker load -i $f -done -for f in $(find $LOCATION/images -name "*.img.xz" 2>/dev/null);do - echo "importing docker image: $f" - xz -d -c $f | docker load -done - -# start the application -${INSTALL_ROOT}/start diff --git a/package b/package index f20f1820..97c24c5d 100755 --- a/package +++ b/package @@ -5,6 +5,5 @@ LOCATION="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" CONFIG=${CONFIG:-${LOCATION}/config.json} SRC=${SRC:-${LOCATION}/src} BUILD_DIR=${BUILD_DIR:-${LOCATION}/build-packages} -INSTALL_SCRIPT=${LOCATION}/install.sh -${LOCATION}/hosted-app-utils/package ${CONFIG} ${SRC} ${INSTALL_SCRIPT} output=${BUILD_DIR} $@ +${LOCATION}/hosted-app-utils/package ${CONFIG} ${SRC} output=${BUILD_DIR} $@ diff --git a/scripts/clientappctl b/scripts/clientappctl new file mode 100755 index 00000000..17d42886 --- /dev/null +++ b/scripts/clientappctl @@ -0,0 +1,171 @@ +#! /usr/bin/env python3 + +import sys, os, argparse, json, shutil, tempfile, subprocess, traceback + +def run_script(script_path: str) -> int: + """ + Helper to handle subprocess execution + Args: + script_path (str): script path to execute + Returns: + int: 0 if both succeed, 1 if any step fails + """ + if not os.path.exists(script_path): + print(f"Error: Script not found at {script_path}", file=sys.stderr) + return 1 + + try: + subprocess.run(f'{script_path}', check=True, capture_output=True, text=True) + return 0 + except subprocess.CalledProcessError as e: + print(f"Error: {os.path.basename(script_path)} failed (Exit {e.returncode})", file=sys.stderr) + print(f"Details: {e.stderr.strip() or e.stdout.strip()}", file=sys.stderr) + return 1 + except PermissionError: + print(f"Error: {os.path.basename(script_path)} is not executable. Check permissions.", file=sys.stderr) + return 1 + +def handle_start(src_dir, install_path): + """ + Start application + + Args: + src_dir (str): Path to source directory (Working directory) + install_path (str): Path to target installation directory + Returns: + int: 0 -> success + """ + + try: + # disable app + if os.path.exists(os.path.join(install_path, 'disable')): + subprocess.check_output(f'{install_path}/disable') + + # copy all source files to installation path + shutil.copytree(src_dir, install_path, dirs_exist_ok=True) + + # start application + if run_script(f'{install_path}/start') != 0: + return 1 + + return 0 + except Exception as ex: + print(f'Error: {ex}') + return 1 + +def handle_stop(install_path: str): + """ + Stop application + Args: + install_path (str): Path to installation directory + Returns: + int: 0 -> success + """ + + if not os.path.exists(os.path.join(install_path, 'stop')): + print(f"Error: stop script not found at {install_path}") + return 1 + + try: + # stop application + if run_script(f'{install_path}/stop') != 0: + return 1 + + return 0 + except subprocess.CalledProcessError as e: + print(f"Error: Stop script failed with exit code {e.returncode}") + print(f"Output: {e.stderr or e.stdout}") + return 1 + +def handle_restart(src_path: str, install_path: str): + """ + Restart application + Args: + install_path (str): Path to installation directory + Returns: + int: 0 -> success + """ + if handle_stop(install_path) != 0: + print("Restart aborted: Stop script execution failed.", file=sys.stderr) + return 1 + + if handle_start(src_path, install_path) != 0: + print("Restart aborted: Start script execution failed.", file=sys.stderr) + return 1 + + print('Application restarted successfully.') + return 0 + + +# script constants +utils_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) +working_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +src_path = os.path.join(working_dir, 'src') +CONFIG_JSON = os.path.join(working_dir, 'config.json') + +templates_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'hosted-app-utils', 'templates')) + +template_files = { + f'{templates_dir}/start': f'{working_dir}/src/start', + f'{templates_dir}/stop': f'{working_dir}/src/stop', + f'{templates_dir}/disable': f'{working_dir}/src/disable' +} + +# command line arguments +parser = argparse.ArgumentParser( + prog="clientappctl", + description="Manage client application operations. [ start, stop, restart ]") + +parser.add_argument("-c", "--config", default=CONFIG_JSON, help=f"Path to application configuration (default: {CONFIG_JSON})") + +subparsers = parser.add_subparsers(dest="command", required=True) + +start_parser = subparsers.add_parser("start", help="Start the application") +stop_parser = subparsers.add_parser("stop", help="Stop the application") +restart_parser = subparsers.add_parser("restart", help="Restart the application") + +def main() -> int: + # parse user args + args = parser.parse_args() + + # load configuration + try: + config_path = args.config + if not os.path.exists(config_path): + raise ValueError("Configuration not found.") + + with open(config_path) as f: + config_data = json.load(f) + name_slug = config_data['name'].strip().replace(' ', '_').replace('-', '_').lower() + org_slug = config_data['org'].strip().replace(' ', '_').replace('-', '_').lower() + version = config_data['version'] + + install_path = f'/var/signalytic/clientapps/{org_slug}/{name_slug}' + + # copy temp files into working dir + for src, dest in template_files.items(): + if not os.path.exists(src): + continue + shutil.copy(src, dest) + + if args.command == "start": + print(f"Starting app: {config_data['name']}-{version}") + + if not os.path.exists(install_path): + os.makedirs(install_path) + return handle_start(src_path, install_path) + elif args.command == "stop": + print(f"Stopping app: {config_data['name']}-{version}") + return handle_stop(install_path) + elif args.command == "restart": + print(f"Restarting app: {config_data['name']}-{version}") + return handle_restart(src_path, install_path) + else: + raise ValueError('Invalid operation passed.') + + except Exception as ex: + print(f'Error: {ex}') + return 1 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/docker-compose.yml b/src/docker-compose.yml old mode 100755 new mode 100644 index 7da8be41..ab7addc1 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -1,6 +1,6 @@ services: streamline-emr: - image: registry.gitlab.com/signalytic/client-external/streamline/streamline-emr/streamline-emr/arm64:v3 + image: registry.gitlab.com/signalytic/client-external/streamline/streamline-emr/streamline-emr/arm64:3.4 container_name: streamline-emr restart: unless-stopped volumes: @@ -18,7 +18,7 @@ services: retries: 5 statistics: - image: registry.gitlab.com/signalytic/client-external/streamline/streamline-emr/statistics/arm64:v3 + image: registry.gitlab.com/signalytic/client-external/streamline/streamline-emr/statistics/arm64:3.1 container_name: streamline-emr-statistics restart: unless-stopped environment: