HEX
Server: Apache
System: Linux host.creative4all.com 4.18.0-553.27.1.el8_10.x86_64 #1 SMP Tue Nov 5 04:50:16 EST 2024 x86_64
User: agrimasfadeltral (1173)
PHP: 8.3.30
Disabled: exec,passthru,shell_exec,system,proc_open,parse_ini_file,show_source
Upload Files
File: //opt/cpmigrate/cpmigrate.py
#!/opt/cpmigrate/venv/bin/python3
"""cPanel migration tool

This is meant to be ran on the destination (new) server.

The tool will perform the migration in stages. All environment checks will be
ran first, to find out what needs to be changed. The end-user will need to
wait for the Environment confirmation dialog to show up to confirm the changes.
After Environment changes are complete, the user migration stage begins.
cPanel packages will be transferred and restored first for all users.
Once all packages are completed, all home directories will be rsynced over.

Stages in order:
pkg_acct - Package up accounts.
pkg_xfer - Transfer account packages to this server.
pkg_restore - Restore account packages on this server.
rsync_home - Rsync home directory to this server.
acct_verify - Perform checks to ensure integrity of transfer.

Choosing a stage to start on will skip all previous stages.
Only do this if you know what you are doing.

If you want the tool to re-check all of the domains, you can give the
following arguments:
    --skipenv all --stage acct_verify --skiprepair

The tool will also perform a mass MySQL database check with auto-repair on the
origin server. The reason for this is due to a common issue where databases
are failing to transfer due to crashed tables or other errors.

In the event of catastrophic migration failure, it can be resumed by providing
the full path to the journal.json in the migration folder.
    --load /var/log/cpmigrate/migration-##############/journal.json
"""
import argparse
import json
import os
import sys
import urllib3
import click
from transfer import Transfer
from environments import ENVIRONMENTS
from environments.base import Environment, FatalMigrationFailure

urllib3.disable_warnings()  # CentOS 5 won't have valid SSL certs.


def main():
    """Main function for interacting with script."""
    if os.geteuid() != 0:
        print("This must be ran as root. Exiting.")
        sys.exit()

    envs: list[Environment] = list(load_environments())
    args = parse_args(envs)

    try:
        if args.delete:
            click.confirm(
                click.style(
                    'WARNING: Resync will run with --delete. Continue?',
                    fg='red',
                    bold=True,
                ),
                abort=True,
            )

        if args.load:
            click.echo(
                click.style(
                    "WARNING: Using --load will ignore all provided args.",
                    fg='red',
                    bold=True,
                )
            )
            loadstate = load_state(args.load)
            transfer = Transfer(loadstate.get('args'), envs)
            transfer.load_state(loadstate)
        else:
            transfer = Transfer(args.__dict__, envs)
            transfer.setup_logging()

            if not args.host:
                raise FatalMigrationFailure(
                    "No host provided, cannot continue!"
                )

            if args.resync_homedirs and args.resync_mail:
                raise FatalMigrationFailure(
                    "Resyncing homedirs will also resync mail, don't use both."
                )
            transfer.setup()

            if args.users:
                transfer.load_users(all_users=False, user_list=args.users)
            else:
                transfer.load_users(all_users=True)

            transfer.load_domains()
            transfer.load_ipaddrs()

            if args.stage:
                transfer.set_stage(args.stage)

            transfer.run_migration()
    except KeyboardInterrupt as keyboard_err:
        print()
        raise FatalMigrationFailure(
            "Interrupt signal has been caught. Exiting."
        ) from keyboard_err

    except click.exceptions.Abort as abort_err:
        print()
        raise FatalMigrationFailure(
            "Migration has been aborted."
        ) from abort_err


def parse_args(envs):
    """Parses arguments."""
    parser = argparse.ArgumentParser(
        description=__doc__, formatter_class=argparse.RawTextHelpFormatter
    )
    arg_group = parser.add_argument_group('Options', 'mostly optional options')
    arg_group.add_argument(
        '-H', '--host', help="hostname of the server to migrate from"
    )
    arg_group.add_argument(
        '-p',
        '--port',
        nargs='?',
        default=22,
        help="ssh port of the original server",
    )
    arg_group.add_argument(
        '-L',
        '--logpath',
        nargs='?',
        default="/var/log/cpmigrate/",
        help="override log path output location",
    )
    arg_group.add_argument(
        '--bwlimit',
        nargs='?',
        type=int,
        default='10000',
        help="changes the bwlimit used for rsync, default is 10000",
    )
    arg_group.add_argument(
        '--skiprepair',
        action='store_true',
        help="skips mass mysql database check+repair",
    )
    env_choices = ['all']
    env_choices.extend([env.name for env in envs if env.name is not None])
    arg_group.add_argument(
        '--skipenv',
        nargs='*',
        help="skips the given environments from being migrated",
        choices=env_choices,
        default=[],
    )
    arg_group.add_argument('--debug', action='store_true', help="enables debug")
    arg_group.add_argument(
        '--noenvconfirm',
        action='store_true',
        help="skips confirmation of environment changes",
    )
    arg_group.add_argument(
        '--delete',
        action='store_true',
        help="use --delete in rsync commands "
        "(will delete emails/files not present on receiver)",
    )
    arg_group.add_argument(
        '--stage',
        nargs='?',
        help="declare migration stage to start on",
        choices=['pkg_xfer', 'pkg_restore', 'rsync_home', 'acct_verify'],
    )
    arg_group.add_argument(
        '--load',
        nargs='?',
        help="continue migration from provided journal.json",
    )
    arg_group.add_argument(
        '-u',
        '--users',
        nargs='+',
        help="declare explicit users to migrate instead of all",
    )

    arg_group.add_argument(
        '--resync-databases',
        help="resync mysql databases for specified users",
        action='store_true',
    )

    arg_group.add_argument(
        '--resync-homedirs',
        help="resync home directories for specified users",
        action='store_true',
    )

    arg_group.add_argument(
        '--resync-mail',
        help="resync mail for specified users",
        action='store_true',
    )

    arg_group.add_argument(
        '--skip-resync-db-backup',
        help="skips local db backup if resyncing databases",
        action='store_true',
    )

    args = parser.parse_args()

    return args


def load_environments():
    """Loads in the environment classes."""
    for environ in ENVIRONMENTS:
        yield environ()


def load_state(json_path):
    """Handles loading the data from journal.json."""
    with open(json_path, encoding="utf-8") as infile:
        data = json.load(infile)
        return data


if __name__ == '__main__':
    main()