# 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()
|