????

Your IP : 52.14.223.136


Current Path : /proc/self/root/usr/lib/Acronis/PyShell/site-tools/
Upload File :
Current File : //proc/self/root/usr/lib/Acronis/PyShell/site-tools/change_machine_id.py

# Script allows to change machine and instance identifiers, it leads to the following changes on agent:
#   - DML database will be cleared
#   - Archives database will be cleared
#   - Certificates for Online Backup will be removed
#   - All scheduled tasks will be removed

# Examples:
#   Create and set new machine and instance IDs, in full and short forms
#     change_machine_id.py --machine-id new --instance-id new
#     change_machine_id.py -m new -i new

#   Set predefined machine and instance IDs
#     change_machine_id.py -m D2A9C54A-5D7F-4CBB-B1DB-1F2D77A012B5 -i 5449D007-3E76-47D1-9BD0-D2CBB03B775E

import acrort
import argparse
import configparser
import glob
import os
import platform
import re
import subprocess
import time
import uuid
import yaml


OS_WINDOWS = 'Windows'
OS_LINUX = 'Linux'
OS_MAC = 'Darwin'

def get_product_data_path():
    return os.path.join(acrort.fs.APPDATA_COMMON, acrort.common.BRAND_NAME)


def get_product_installation_path():
    system = platform.system()
    if system == OS_WINDOWS:
        key = r'SOFTWARE\{}\Installer'.format(acrort.common.BRAND_NAME)
        return registry_read_string(key, 'TargetDir')
    elif system == OS_LINUX:
        return '/usr/lib/' + acrort.common.BRAND_NAME
    elif system == OS_MAC:
        return '/Library/Application Support/BackupClient/' + acrort.common.BRAND_NAME

    acrort.common.make_logic_error('Unsupported operating system: ' + system).throw()


def get_scheduler_path():
    system = platform.system()
    if system == OS_WINDOWS:
        scheduler_path = os.path.join(get_product_installation_path(), 'BackupAndRecovery', 'schedmgr')
    elif system == OS_LINUX:
        scheduler_path = '/usr/sbin/schedmgr'
    elif system == OS_MAC:
        scheduler_path = '/Library/Application Support/BackupClient/{}/sbin/schedmgr'.format(acrort.common.BRAND_NAME)
    else:
        acrort.common.make_logic_error('Unsupported operating system: ' + system).throw()

    return scheduler_path


def get_settings_key():
    return r'SOFTWARE\{}\BackupAndRecovery\Settings'.format(acrort.common.BRAND_NAME)


def get_machine_settings_key():
    return get_settings_key() + r'\MachineManager'


def is_guid(key):
    RE_UUID = re.compile("[0-F]{8}-[0-F]{4}-[0-F]{4}-[0-F]{4}-[0-F]{12}", re.I)
    return bool(RE_UUID.match(key))


def registry_read_string(key_name, value_name, open_hive=None):
    root_reg = acrort.registry.open_system_hive(hive=open_hive)
    if key_name not in root_reg.subkeys:
        acrort.common.make_logic_error(
            "Key '{}' not found. May be MMS service is not installed".format(key_name)).throw()
    key = root_reg.subkeys.open(key_name=key_name)
    if value_name not in key.values:
        acrort.common.make_logic_error(
            "Value '{}' not found. May be MMS service is not installed".format(value_name)).throw()
    value = key.values.open(value_name=value_name)
    return value.get(acrort.registry.TYPE_SZ)


def registry_write_string(key_name, value_name, data, open_hive=None):
    root_reg = acrort.registry.open_system_hive(hive=open_hive)
    if key_name not in root_reg.subkeys:
        acrort.common.make_logic_error(
            "Key '{}' not found. May be MMS service is not installed".format(key_name)).throw()
    key = root_reg.subkeys.open(key_name=key_name)
    if value_name not in key.values:
        acrort.common.make_logic_error(
            "Value '{}' not found. May be MMS service is not installed".format(value_name)).throw()
    value = key.values.open(value_name=value_name)
    return value.set(data, acrort.registry.TYPE_SZ)


def registry_delete_key(key_name, value_name, open_hive=None):
    root_reg = acrort.registry.open_system_hive(hive=open_hive)
    if key_name not in root_reg.subkeys:
        return
    key = root_reg.subkeys.open(key_name=key_name)
    if value_name not in key.values:
        return
    key.values.delete(value_name=value_name)


def get_current_machine_id():
    return registry_read_string(get_machine_settings_key(), 'MMSCurrentMachineID')


def set_current_machine_id(machine_id):
    # Set machine ID in the registry
    registry_write_string(get_machine_settings_key(), 'MMSCurrentMachineID', machine_id)

    # Set machine ID in the aakore config
    aakore_config_path = get_aakore_config_file_path()

    with open(aakore_config_path, 'w') as aakore:
        try:
            config = {}
            config['id'] = machine_id.lower()
            yaml.dump(config, aakore, default_flow_style=False)
        except Exception as e:
            acrort.common.make_logic_error('Failed to modify aakore config with error: ' + str(e)).throw()

def get_current_instance_id():
    return registry_read_string(get_machine_settings_key(), 'InstanceID')


def set_current_instance_id(instance_id):
    registry_write_string(get_machine_settings_key(), 'InstanceID', instance_id)


def is_service_running(service_name):
    system = platform.system()
    if system == OS_WINDOWS:
        args = ['sc', 'query', service_name]
        ps = subprocess.Popen(args, stdout=subprocess.PIPE)
        output = ps.communicate()[0]
        return 'STOPPED' not in str(output)
    elif system in [OS_MAC, OS_LINUX]:
        ps = subprocess.Popen(('ps', 'aux'), stdout=subprocess.PIPE)
        output = ps.communicate()[0]
        return ('/' + service_name) in str(output)

    acrort.common.make_logic_error('Unsupported operating system: ' + system).throw()


def get_systemd_service_stop_timeout_sec(service_name, default_value):
    config = configparser.ConfigParser()
    config.read('/etc/systemd/system/' + service_name + '.service')
    value = str(config.get('Service', 'TimeoutStopSec', fallback=default_value))

    if value.endswith("min"):
        return int(value.replace('min', '')) * 60
    else:
        return int(value)


def start_service(windows_name, unix_name, display_name):
    try:
        system = platform.system()
        if system == OS_WINDOWS:
            args = ['sc', 'start', windows_name]
        elif system == OS_LINUX:
            args = ['service', unix_name, 'start']
        elif system == OS_MAC:
            args = ['launchctl', 'start', unix_name]
        else:
            acrort.common.make_logic_error('Unsupported operating system: ' + system).throw()

        print('Execute command: {}'.format(' '.join(args)))

        subprocess.run(args, stdout=subprocess.DEVNULL, check=True)
    except Exception as e:
        print('Can\'t start {} service: {}'.format(display_name, str(e)))


def stop_service(windows_name, unix_name, display_name, is_service_running):
    try:
        system = platform.system()
        if system == OS_WINDOWS:
            args = ['sc', 'stop', windows_name]
            timeout = 60
        elif system == OS_LINUX:
            args = ['service', unix_name, 'stop']
            timeout = get_systemd_service_stop_timeout_sec(unix_name, 60)
        elif system == OS_MAC:
            args = ['launchctl', 'stop', unix_name]
            timeout = 60
        else:
            acrort.common.make_logic_error('Unsupported operating system: ' + system).throw()

        print('Execute command: {}'.format(' '.join(args)))

        subprocess.run(args, stdout=subprocess.DEVNULL, check=True, timeout=timeout)
    except subprocess.CalledProcessError as e:
        acrort.common.make_logic_error(
            'Can\'t stop {} service with error: {}'.format(display_name, str(e))).throw()
    else:
        # Lookup for target process, wait if it is still here
        wait_reattempts = 10
        while wait_reattempts:
            time.sleep(10)
            if not is_service_running():
                break

            wait_reattempts = wait_reattempts - 1
            if not wait_reattempts:
                acrort.common.make_logic_error(
                    'Can\'t stop {} service, please stop it manually.'.format(display_name)).throw()


def stop_service_process():
    system = platform.system()
    if system == OS_WINDOWS:
        # Kill service-processes too
        args = ['taskkill' , '/FI', 'IMAGENAME eq service_process.exe', '/F', '/T']
        subprocess.run(args, stdout=subprocess.DEVNULL, check=False)


# Aakore
def get_aakore_config_file_path():
    system = platform.system()
    if system == OS_WINDOWS:
        aakore_path = os.path.join(get_product_data_path(), r'Agent\var\aakore\reg.yml')
    elif system == OS_LINUX:
        aakore_path = '/opt/acronis/var/aakore/reg.yml'
    elif system == OS_MAC:
        aakore_path = '/Library/Application Support/{}/Agent/var/aakore/reg.yml'.format(acrort.common.BRAND_NAME)
    else:
        acrort.common.make_logic_error('Unsupported operating system: ' + system).throw()

    return aakore_path


def is_aakore_service_running():
    return is_service_running('aakore')


def start_aakore_service():
    start_service('aakore', 'aakore', 'Aakore')


def stop_aakore_service():
    stop_service('aakore', 'aakore', 'Aakore', is_aakore_service_running)


# MMS
def is_mms_service_running():
    return is_service_running('mms')


def start_mms_service():
    start_service('mms', 'acronis_mms', 'MMS')


def stop_mms_service():
    stop_service('mms', 'acronis_mms', 'MMS', is_mms_service_running)
    stop_service_process()


# EmergencyUpdater
def is_emergency_updater_service_running():
    system = platform.system()
    if system == OS_WINDOWS:
        args = ['sc', 'query', 'emergency-updater']
        ps = subprocess.Popen(args, stdout=subprocess.PIPE)
        ps.wait()
        if ps.returncode != 0:  # service not installed
            return False

    return is_service_running('emergency-updater')


def start_emergency_updater_service():
    start_service('emergency-updater', 'emergency-updater', 'EmergencyUpdater')


def stop_emergency_updater_service():
    stop_service('emergency-updater', 'emergency-updater', 'EmergencyUpdater', is_emergency_updater_service_running)
    stop_service_process()


def remove_files(path):
    files = glob.glob(path)
    for f in files:
        reattempts = 10
        while reattempts:
            try:
                os.remove(f)
            except FileNotFoundError:
                break
            except PermissionError as e:
                reattempts = reattempts - 1
                if not reattempts:
                    print(str(e))
                    raise
                time.sleep(10)
            else:
                break


def drop_acp_agent_caches():
    system = platform.system()
    if system == OS_WINDOWS:
        acp_agent_aakore_cache_path = os.path.join(get_product_data_path(), 'Agent', 'var', 'atp-agent', 'aakore_proxy_cache.json')
    elif system == OS_LINUX:
        acp_agent_aakore_cache_path = '/opt/acronis/var/atp-agent/aakore_proxy_cache.json'
    elif system == OS_MAC:
        acp_agent_aakore_cache_path = '/Library/Application Support/Acronis/Agent/var/atp-agent/aakore_proxy_cache.json'
    else:
        acrort.common.make_logic_error('Unsupported operating system: ' + system).throw()

    remove_files(acp_agent_aakore_cache_path)


def drop_acp_updater_caches():
    system = platform.system()
    if system == OS_WINDOWS:
        acp_updater_config_cache_path = os.path.join(get_product_data_path(), 'Agent', 'var', 'atp-downloader', 'atp-downloader.json')
    elif system == OS_LINUX:
        acp_updater_config_cache_path = '/opt/acronis/var/atp-downloader/atp-downloader.json'
    elif system == OS_MAC:
        acp_updater_config_cache_path = '/Library/Application Support/Acronis/Agent/var/atp-downloader/atp-downloader.json'
    else:
        acrort.common.make_logic_error('Unsupported operating system: ' + system).throw()

    remove_files(acp_updater_config_cache_path)


def drop_acp_sh_inventory_caches():
    system = platform.system()
    if system == OS_WINDOWS:
        acp_sh_inventory_instance_id_cache_path = os.path.join(get_product_data_path(), 'Agent', 'var', 'sh-inventory', '.resource')
    elif system == OS_LINUX:
        acp_sh_inventory_instance_id_cache_path = '/opt/acronis/var/sh-inventory/.resource'
    elif system == OS_MAC:
        acp_sh_inventory_instance_id_cache_path = '/Library/Application Support/Acronis/Agent/var/sh-inventory/.resource'
    else:
        acrort.common.make_logic_error('Unsupported operating system: ' + system).throw()

    remove_files(acp_sh_inventory_instance_id_cache_path)


def drop_emergency_updater_service_caches():
    system = platform.system()
    if system == OS_WINDOWS:
        emergency_updater_aakore_cache_path = os.path.join(get_product_data_path(), 'Agent', 'var', 'emergency-updater', 'resources_cache.json')
        emergency_updater_emergency_config_cache_path = os.path.join(get_product_data_path(), 'Agent', 'var', 'emergency-updater', 'emergencyUpdaterResourcesCache.json')
    elif system == OS_LINUX:
        emergency_updater_aakore_cache_path = '/opt/acronis/var/emergency-updater/resources_cache.json'
        emergency_updater_emergency_config_cache_path = '/opt/acronis/var/emergency-updater/emergencyUpdaterResourcesCache.json'
    elif system == OS_MAC:
        emergency_updater_aakore_cache_path = '/Library/Application Support/Acronis/Agent/var/emergency-updater/resources_cache.json'
        emergency_updater_emergency_config_cache_path = '/Library/Application Support/Acronis/Agent/var/emergency-updater/emergencyUpdaterResourcesCache.json'
    else:
        acrort.common.make_logic_error('Unsupported operating system: ' + system).throw()

    remove_files(emergency_updater_aakore_cache_path)
    remove_files(emergency_updater_emergency_config_cache_path)

def drop_databases():
    path = get_product_data_path()

    archives_db_path = os.path.join(path, 'BackupAndRecovery', 'archives_cache.*')
    print('Deleting DB at: {} ...'.format(archives_db_path))
    remove_files(archives_db_path)

    dml_db_path = os.path.join(path, 'BackupAndRecovery', 'MMSData', 'DML', 'F4CEEE47-042C-4828-95A0-DE44EC267A28.*')
    print('Deleting DB at: {} ...'.format(dml_db_path))
    remove_files(dml_db_path)

    if platform.system() == OS_MAC:
        old_dml_db_path = os.path.join(get_product_installation_path(), 'var_lib', 'Acronis', 'BackupAndRecovery', 'MMSData', 'DML', 'F4CEEE47-042C-4828-95A0-DE44EC267A28.*')
        print('Deleting DB at: {} ...'.format(old_dml_db_path))
        remove_files(old_dml_db_path)


def drop_online_backup_certificates():
    path = get_product_data_path()

    ob_cert_path = os.path.join(path, 'BackupAndRecovery', 'OnlineBackup', 'Default', '*')
    print('Deleting certificates at: {} ...'.format(ob_cert_path))
    remove_files(ob_cert_path)


def drop_cached_machine_names():
    registry_delete_key(get_settings_key(), 'CachedHostName')
    registry_delete_key(get_settings_key(), 'CachedMachineName')


def drop_scheduled_tasks():
    args = [get_scheduler_path(), 'task', 'zap']
    print('Execute command: {}'.format(' '.join(args)))
    subprocess.run(args, stdout=subprocess.DEVNULL, check=True)


def main():
    parser = argparse.ArgumentParser(description='Change parameters for Acronis Managed Machine Service (MMS)')
    
    parser.add_argument(
        '-m', '--machine-id',
        required=True,
        nargs=1,
        help='Machine identifier, in form <GUID> or <new> to set auto-generated new identifier')

    parser.add_argument(
        '-i', '--instance-id',
        required=False,
        nargs=1,
        help='Instance identifier, in form <GUID> or <new> to set auto-generated new identifier')

    args = parser.parse_args()

    machine_id = args.machine_id[0]
    if machine_id == 'new' or machine_id == '<new>':
        machine_id = str(uuid.uuid4())

    if not is_guid(machine_id):
        print("Machine ID: invalid GUID format: {}".format(machine_id))
        return

    machine_id = machine_id.upper()
    current_machine_id = get_current_machine_id()
    
    change_instance_id = args.instance_id != None
    if change_instance_id:
        instance_id = args.instance_id[0]
        if instance_id == 'new' or instance_id == '<new>':
            instance_id = str(uuid.uuid4())

        if not is_guid(instance_id):
            print("Instance ID: invalid  GUID format: {}".format(instance_id))
            return

        instance_id = instance_id.upper()
        current_instance_id = get_current_instance_id()

        if machine_id == instance_id:
            print('Machine ID and Instance ID are identical. They should be different!')
            return

    emergency_updater_running = False
    if is_emergency_updater_service_running():
        emergency_updater_running = True
        print("Stopping EmergencyUpdater service...")
        stop_emergency_updater_service()
        print("Done.\n")

    if is_mms_service_running():
        print("Stopping MMS service...")
        stop_mms_service()
        print("Done.\n")

    if is_aakore_service_running():
        print("Stopping Aakore service...")
        stop_aakore_service()
        print("Done.\n")

    print("Clean databases...")
    drop_databases()
    print("Done.\n")

    print("Removing scheduled tasks...")
    drop_scheduled_tasks()
    print("Done.\n")

    print("Removing certificates for online backup...")
    drop_online_backup_certificates()
    print("Done.\n")

    print("Removing cached machine names...")
    drop_cached_machine_names()
    print("Done.\n")

    print("Clean acp agent caches...")
    drop_acp_agent_caches()
    print("Done.\n")

    print("Clean acp updater caches...")
    drop_acp_updater_caches()
    print("Done.\n")

    print("Clean acp sh-inventory caches...")
    drop_acp_sh_inventory_caches()
    print("Done.\n")

    if emergency_updater_running:
        print("Clean EmergencyUpdater service caches...")
        drop_emergency_updater_service_caches()
        print("Done.\n")

    set_current_machine_id(machine_id)
    print("Machine ID has changed from '{}' to '{}'".format(current_machine_id, machine_id))

    if change_instance_id:
        set_current_instance_id(instance_id)
        print("Instance ID has changed from '{}' to '{}'".format(current_instance_id, instance_id))

    print("\nStarting Aakore service...")
    start_aakore_service()
    print("Done.\n")

    print("Starting MMS service...")
    start_mms_service()
    print("Done.\n")

    if emergency_updater_running:
        print("Starting EmergencyUpdater service...")
        start_emergency_updater_service()
        print("Done.\n")

    print("Successfully finished.")


if __name__ == '__main__':
    main()