#!/bin/bash apt update && apt install -y sudo # Orchestrator app code create_orchestrator_app_code() { # Create the app.py Python code for managing orchestrator if [ -f app.py ]; then rm app.py fi cat < app.py import os import json import time import requests import subprocess import sys import zipfile get_node_config_response="" node_url="" docker_compose_core="" version='1.0.0' last_status="" download_possible = True def check_config_url_file(filename): if not os.path.exists(filename): print(f"File '{filename}' not found.") return False try: # Load config with open(filename, "r") as f: config_data = json.load(f) if "url" not in config_data: print("Missing 'url' key in config.") return False url = config_data["url"].strip() # Ensure url starts with https:// if not url.startswith("https://"): print("URL does not start with https:// — fixing...") # Remove any existing protocol if url.startswith("http://"): url = url[len("http://"):] elif url.startswith("https://"): url = url[len("https://"):] # Add https:// fixed_url = "https://" + url config_data["url"] = fixed_url # Save back to file with open(filename, "w") as f: json.dump(config_data, f, indent=4) print(f"URL updated to: {fixed_url}") else: print("URL already starts with https:// — no changes made.") return True except json.JSONDecodeError: print("Invalid JSON format and/or config file.") return False def check_config_url(): init = False while not init: init = check_config_url_file("config.json") time.sleep(1) def check_config_file(filename): global node_udid global node_url try: with open(filename, 'r') as f: config_data = json.load(f) if 'udid' in config_data and 'url' in config_data: if config_data['udid']!="" and config_data['url']!="": print("All good!") # print(config_data['udid']) # print(config_data['url']) node_udid = config_data['udid'] node_url = config_data['url'] # print(node_udid) # print(node_url) return True else: print("Missing values") return False else: print("Missing keys") except FileNotFoundError: print("File 'config.json' not found.") def check_config(): init = False while not init: init = check_config_file("config.json") time.sleep(1) def save_secret_to_file(secret): print(secret) try: with open('secret.json', 'w') as file: json.dump({'secret': secret}, file) print("Token saved to 'secret.json' file.") except Exception as e: print(f"Failed to save token to file: {e}") def reset_secret_file(): try: with open('secret.json', 'w') as file: file.write('{"secret":""}') print("Reset 'secret.json' file.") except Exception as e: print(f"Failed to save token to file: {e}") def get_secret(): global node_udid global node_url global node_secret print (node_udid) print (node_url) url=node_url+'/api/v1/opcua_orchestrators/lock_token.json?udid='+node_udid print(url) response = requests.post(url) if response.status_code == 200: secret = response.json().get('token') print(secret) if secret!="null" and secret!=None: save_secret_to_file(secret) node_secret = secret return True else: print("Secret not available") return False else: print("Get secret failed") return False def check_secret_file(secret_filename): global node_secret try: with open(secret_filename, 'r') as f: secret_data = json.load(f) print(secret_data['secret']) if 'secret' in secret_data and secret_data['secret']!=None: if secret_data['secret']!="": print ("Secret is OK") node_secret = secret_data['secret'] return True else: print ("File is empty or incorrect") return get_secret() else: print ("Missing key") reset_secret_file() return False except FileNotFoundError: print("File 'secret.json' not found.") return False def check_secret(): secret = False while not secret: secret = check_secret_file("secret.json") time.sleep(5) def generate_docker_compose(json_data, json_core): print("Entering generate_docker_compose") global node_url docker_compose="" # print("json_core") # print(json_core) if 'opcua_docker_compose' in json_core: docker_compose_dict = json.loads(json_core['opcua_docker_compose']) if docker_compose_dict['core'] and docker_compose_dict['image_path']: print("Core and path are available") docker_compose = docker_compose_dict['core'] # print (docker_compose) image_path = docker_compose_dict['image_path'] # print(image_path) for machine in json_data["tablets"]: service_name = str(machine["id"]) docker_compose['services'][service_name] = { 'image': image_path, # Replace "your_docker_image_name" with your actual Docker image name 'environment': { 'TEEPTRAK_MACHINE_UUID': machine["unique_device_id"], 'TEEPTRAK_API_TOKEN': machine["token"], 'TEEPTRAK_SERVER_URL': node_url, 'TEEPTRAK_MONGO_URL':'mongodb://localhost:27017' } } # print(docker_compose) try: with open('docker-compose.yml', 'w') as file: json.dump(docker_compose, file, indent=4) print("Docker compose saved in file.") except Exception as e: print(f"Failed to save token to file: {e}") def run_docker_compose(): command = ['sudo', 'docker', 'compose', '-f', 'docker-compose.yml', 'up', '-d', '--remove-orphans'] try: # Run the command and capture the output output = subprocess.check_output(command, stderr=subprocess.STDOUT) return print("OK","\n",output.decode('utf-8')) # return true except subprocess.CalledProcessError as e: # If there's an error, print the error message return print("Error:", e.output.decode('utf-8')) def get_node_config(): # Get node config from website url=node_url+'/api/v1/opcua_orchestrators.json?udid='+node_udid+','+node_secret url_docker_compose=node_url+'/api/v1/application_options/opcua_docker_compose.json?udid='+node_udid+','+node_secret # print(url) # Compare new configuration with previous one global get_node_config_response global docker_compose_core global docker_credentials response = requests.get(url) response_docker_compose = requests.get(url_docker_compose) #response_docker_credentials = requests.get(url_docker_credentials) #print("Docker compose") # #print(response_docker_compose.json()) if response.content!=get_node_config_response or response_docker_compose!=docker_compose_core: print ("Contents are different") get_node_config_response=response.content docker_compose_core=response_docker_compose.content print ("Saved response") print (get_node_config_response) if response.status_code == 200 and response_docker_compose.status_code == 200: generate_docker_compose(response.json(),response_docker_compose.json()) run_docker_compose() return True else: print("Get node config failed") return False else: # Nothing to do print ("No change in node configuration") return False def get_service_status(service_name): try: # Run the systemctl command to get the status of the service result = subprocess.run(['systemctl', 'status', service_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) # Check if the command was successful if result.returncode == 0: # Iterate through the output lines to find the "Active:" line for line in result.stdout.splitlines(): if 'Active:' in line: # Return the line containing "Active:" return line.strip() else: # If the command fails, return an error message return f"Failed to get status for service '{service_name}': {result.stderr.strip()}" except Exception as e: return f"An error occurred: {str(e)}" def send_orchestrator_status(status_text): global node_udid global node_url global node_secret #url = f'{node_url}/api/v1/opcua_orchestrators/status.json?udid={node_udid},{node_secret}' url=node_url+'/api/v1/opcua_orchestrators/status.json?udid='+node_udid+','+node_secret payload = { "status": status_text, "version": version } try: response = requests.put(url, json=payload) response.raise_for_status() # Raises an exception for HTTP errors print("Status sent successfully:", response.status_code) except requests.exceptions.RequestException as e: print("Error sending status:", e) def download_and_upgrade(target_version): global download_possible # Constructing the URL url = f'{node_url}/api/v1/opcua_orchestrator_versions/{target_version}?udid={node_udid},{node_secret}' # Setting up the file paths base_dir = os.path.dirname(os.path.abspath(__file__)) # Directory where the Python script is located upgrade_dir = os.path.join(base_dir, 'upgrade') zip_file_path = os.path.join(upgrade_dir, 'orchestrator.zip') # Ensure the upgrade directory exists os.makedirs(upgrade_dir, exist_ok=True) try: # Downloading the ZIP file print(f"Downloading file from {url}...") response = requests.get(url) response.raise_for_status() # Raises an exception for HTTP errors # Saving the ZIP file with open(zip_file_path, 'wb') as file: file.write(response.content) print(f"Downloaded file saved to {zip_file_path}") # Unzipping the file with zipfile.ZipFile(zip_file_path, 'r') as zip_ref: zip_ref.extractall(upgrade_dir) print(f"Unzipped the file to {upgrade_dir}") # Deleting the ZIP file if extraction was successful os.remove(zip_file_path) print(f"Deleted the ZIP file {zip_file_path}") except requests.exceptions.RequestException as e: print("Error during download:", e) except zipfile.BadZipFile: print("Error: The downloaded file is not a valid ZIP file.") except Exception as e: print("An unexpected error occurred:", e) print("File has been successfully downloaded") download_possible = False os.system("sudo systemctl start orchestrator-upgrade") def check_orchestrator_version(): global last_status global download_possible global version #url = f'{node_url}/api/v1/opcua_orchestrators/version.json?udid={node_udid},{node_secret}' url=node_url+'/api/v1/opcua_orchestrators/version.json?udid='+node_udid+','+node_secret try: # Sending the GET request response = requests.get(url) response.raise_for_status() # Raises an exception for HTTP errors # Parsing the JSON response data = response.json() # Extracting target_version from the JSON response target_version = data.get('target_version') if target_version is None: print("Error: 'target_version' not found in the response") #last_status="Error: 'target_version' not found in the response" return # Comparing the versions if version == target_version: print("Version is up-to-date.") #last_status="Version is up-to-date." # Place the code here for when versions are equal # For example: pass elif version != target_version and download_possible == False: print("Update process already launched, nothing to do") else: print(f"Version mismatch: Current version {version}, Target version {target_version}") last_status=f"Update needed to target version {target_version}" download_and_upgrade(target_version) # Place the code here for when versions are different # For example: Trigger an update process or notify someone except requests.exceptions.RequestException as e: print("Error contacting the server:", e) except ValueError: print("Error parsing the JSON response") if __name__ == "__main__": # We go through the configuration loops once every PY app startup. check_config_url() check_config() check_secret() loop_node_config_check=True while loop_node_config_check: check_orchestrator_version(); current_service_status = get_service_status("orchestrator-main"); print (current_service_status); send_orchestrator_status(current_service_status); time.sleep(20) EOF } # Create orchestrator service create_orchestrator_service() { # Get the directory where the script is being executed local script_dir="$(pwd)" # Define the path to the systemd service file local service_file="/etc/systemd/system/orchestrator-main.service" # Define the content of the service file with dynamic paths local service_content="[Unit] Description=Orchestrator Main Service After=network.target After=docker.service Wants=docker.service [Service] WorkingDirectory=$script_dir ExecStart=/usr/bin/python3 $script_dir/app.py Restart=always [Install] WantedBy=multi-user.target" # Check if the service file already exists if [ -f "$service_file" ]; then echo "Service file exists. Replacing it..." else echo "Creating new service file..." fi # Write the content to the service file echo "$service_content" | sudo tee "$service_file" > /dev/null echo "Orchestrator main service file created/replaced successfully." } # Orchestrator app code create_orchestrator_upgrade_app_code() { # Create the app.py Python code for managing orchestrator if [ -f upgrade.py ]; then rm upgrade.py fi cat < upgrade.py import os import shutil import subprocess import time # Get the current working directory application_folder = os.getcwd() # Define the paths upgrade_folder = os.path.join(application_folder, "upgrade") app_file_path = os.path.join(upgrade_folder, "app.py") destination_path = os.path.join(application_folder, "app.py") # Define the service names main_service_name = "orchestrator-main" upgrade_service_name = "orchestrator-upgrade" def service_action(service_name, action): """Start, stop, or restart a service.""" print(f"{action.capitalize()}ing the {service_name} service...") subprocess.run(["sudo","systemctl", action, service_name], check=True) print(f"{service_name} service {action}ed successfully.") def check_service_status(service_name): """Check the status of a service.""" print(f"Checking the status of {service_name} service...") while True: result = subprocess.run(["sudo","systemctl", "is-active", service_name], capture_output=True, text=True) if result.stdout.strip() == "inactive": print(f"{service_name} service has stopped.") break else: print(f"{service_name} service is still stopping...") time.sleep(2) # Wait for 2 seconds before checking again def main(): # Step 1: Check if the app.py file exists in the upgrade folder if os.path.exists(app_file_path): print(f"Found {app_file_path}. Proceeding with the upgrade process.") # Step 2: Stop the orchestrator-main service service_action(main_service_name, "stop") # Step 3: Wait for the service to completely stop check_service_status(main_service_name) # Step 4: Copy app.py from the upgrade folder to the application folder print(f"Copying {app_file_path} to {destination_path}...") shutil.copy2(app_file_path, destination_path) print(f"File copied successfully.") # Step 5: Start the orchestrator-main service service_action(main_service_name, "start") print("Upgrade process completed successfully.") # Step 6: Shut down the orchestrator-upgrade service service_action(upgrade_service_name, "stop") else: print(f"No {app_file_path} found. No actions taken.") # Step 6: Shut down the orchestrator-upgrade service service_action(upgrade_service_name, "stop") if __name__ == "__main__": main() EOF } # Create orchestrator upgrade service create_orchestrator_upgrade_service() { # Get the directory where the script is being executed local script_dir="$(pwd)" # Define the path to the systemd service file local service_file="/etc/systemd/system/orchestrator-upgrade.service" # Define the content of the service file with dynamic paths local service_content="[Unit] Description=Orchestrator Upgrade Service After=network.target [Service] WorkingDirectory=$script_dir ExecStart=/usr/bin/python3 $script_dir/upgrade.py Restart=always [Install] WantedBy=multi-user.target" # Check if the service file already exists if [ -f "$service_file" ]; then echo "Service file exists. Replacing it..." else echo "Creating new service file..." fi # Write the content to the service file echo "$service_content" | sudo tee "$service_file" > /dev/null echo "Orchestrator upgrade service file created/replaced successfully." } is_secret_empty() { secret_value=$(jq -r '.secret' secret.json) if [ "$secret_value" = "" ]; then return 0 else return 1 fi } # Prompt user for input read -p "Enter the UDID: " udid # Check if parameters are provided if [ -z "$udid" ]; then echo "You must provide the UDID as a named parameter (udid=)." exit 1 fi # Prompt and validate the URL format while true; do read -p "Enter the URL (must start with https://): " url # Regex to match 'https://' followed by a valid domain or IP + optional port or path if [[ "$url" =~ ^https://[a-zA-Z0-9.-]+(:[0-9]+)?(/.*)?$ ]]; then # URL format is valid, now test connectivity echo "🔍 Checking connectivity to $url ..." if [ "$(curl --head --connect-timeout 5 -s -o /dev/null -w "%{http_code}" "$url")" = "200" ]; then echo "✅ Successfully reached $url" break else echo "⚠️ Could not reach $url" while true; do read -p "Do you want to continue anyway (y), change URL (c), or exit (n)? [y/c/n]: " proceed case "$proceed" in y|Y) echo "Proceeding with unreachable URL: $url" break 2 # break out of both loops ;; c|C) echo "🔁 Let's try entering a new URL..." break # go back to outer URL input loop ;; n|N) echo "❌ Exiting script." exit 1 ;; *) echo "❓ Invalid choice. Please enter y, c, or n." ;; esac done fi else echo "❌ Invalid URL format. Please use a full URL starting with 'https://'." fi done if [ ! -f secret.json ] || ( [ -f secret.json ] && is_secret_empty ); then # Check if the config.json file exists and delete it if it does if [ -f config.json ]; then rm config.json fi # Create the config.json file cat < config.json { "udid": "$udid", "url": "$url" } EOF echo "config.json created successfully with the provided parameters." # Check if the secret.json file exists and delete it if it does if [ -f secret.json ]; then rm secret.json fi cat < secret.json { "secret": "" } EOF echo "secret.json created successfully." else echo "secret.json already exists" fi # Fun to check if a tool exists command_exists(){ command -v "$1" >/dev/null 2>&1 } # Update package list and install prerequisites for adding new repositories echo "Installation of Python 3" # sleep 2 # sudo apt-get update -y # sudo apt-get upgrade -y # sudo apt-get install -y software-properties-common # sudo apt-get install -y apt-transport-https # sudo apt-get install -y ca-certificates # sudo apt-get install -y curl # sudo apt-get install -y gnupg-agent # Install Python3 and pip if not already installed if command_exists python3; then echo "Python3 is already installed" else apt-get update -y apt-get install -y python3 python3-pip python3-venv python3-dev fi if command_exists pip3; then echo "Python3-pip is already installed" else apt-get update -y apt-get install -y python3-pip fi echo "Python3 and Pip installation complete." # Install Docker if not already installed # Add Docker's official GPG key echo "Installation of Docker" sleep 5 if command_exists docker; then echo "Docker is already installed" else curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # Set up the Docker repository echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # Update package list and install Docker sudo apt-get update -y sudo apt-get install -y docker-ce docker-ce-cli containerd.io fi echo "Docker installation complete." # # Install Docker Compose if not already installed # echo "Installation of Docker-compose" # sleep 2 # DOCKER_COMPOSE_VERSION="1.29.2" # if command_exists docker-compose; then # echo "Doker-compose is already installed" # else # sudo curl -L "https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # sudo chmod +x /usr/local/bin/docker-compose # fi # echo "Docker-compose installation complete." # sleep 2 # echo "Installation complete" # Verify Python installation python3 --version pip3 --version # Verify Docker installation sudo docker --version # Verify Docker Compose installation # docker-compose --version echo "Implement orchestrator upgrade code & service" create_orchestrator_upgrade_app_code create_orchestrator_upgrade_service echo "Implement orchestrator code & service" create_orchestrator_app_code create_orchestrator_service #for i in {1..20} #do # sleep 0.1 # echo -n "." #done # Add sudoers rights for services # Get the current username CURRENT_USER=$(whoami) # Sudoers entry SUDOERS_ENTRY="$CURRENT_USER ALL=(ALL) NOPASSWD: /bin/systemctl start orchestrator-main, /bin/systemctl stop orchestrator-main, /bin/systemctl enable orchestrator-main, /bin/systemctl start orchestrator-update, /bin/systemctl stop orchestrator-update" # Backup the sudoers file sudo cp /etc/sudoers /etc/sudoers.bak # Check if the entry already exists in the sudoers file if sudo grep -Fxq "$SUDOERS_ENTRY" /etc/sudoers; then echo "Sudoers entry already exists." else # Add the entry to the sudoers file echo "$SUDOERS_ENTRY" | sudo tee -a /etc/sudoers > /dev/null echo "Sudoers entry added successfully." fi # Reload systemd services sudo systemctl daemon-reload # Start orchestrator-main.service sudo systemctl start orchestrator-main.service # Enable at start-up orchestrator-main.service sudo systemctl enable orchestrator-main.service # Start orchestrator-main.service sudo systemctl start orchestrator-upgrade.service # Enable at start-up orchestrator-main.service sudo systemctl enable orchestrator-upgrade.service