#!/usr/bin/env python3

from collections import deque
from pycman import config
import argparse
import logging
import pyalpm
import sys

parser = argparse.ArgumentParser(description='Re-install Pacman and its dependencies with busybox to recover a live system. This script should be run on a working Arch Linux system of the same architecture as the target system. Read the generated README.txt file for further instructions.')
parser.add_argument(
  '-c', '--config', default='/etc/pacman.conf',
  help='Pacman configuration file. Default: %(default)s'
)



def main(args=None):
  logging.basicConfig(
    format='%(levelname)s: %(message)s',
    level=logging.ERROR
  )
  pargs = parser.parse_args(args)
  h = config.init_with_config(pargs.config)
  for db in h.get_syncdbs():
    if db.name == 'core':
      core_db = db
      break
  else:
    logging.error('failed to find core repository')
    sys.exit(1)

  # The second item in the tuple is used to order dependencies.
  pending = deque((
    (core_db.get_pkg('pacman'), 0),
  ))
  required = dict()

  while pending:
    pkg, dep_level = pending.popleft()
    required[pkg.name] = dep_level
    dep_level += 1
    for d in pkg.depends:
      provider = pyalpm.find_satisfier(core_db.pkgcache, d)
      if provider.name not in required:
        pending.append((provider, dep_level))
      else:
        required[provider.name] = max(required[provider.name], dep_level)

  download_script = '''#!/bin/sh
busybox mkdir -p pkg
cd pkg

'''

  unpack_script = '''#!/bin/sh
busybox mkdir -p "$1"

'''

  for pkgname, level in sorted(required.items(), key=lambda x: -x[1]):
    pkg = core_db.get_pkg(pkgname)
    download_script += 'busybox wget \'{}/{}\'\n'.format(core_db.servers[0], pkg.filename)
    unpack_script += '''busybox echo 'unpacking {}'
busybox tar -xf \'pkg/{}\' -C "$1"
'''.format(pkg.name, pkg.filename)

  unpack_script += '''
# clean up extraneous files
busybox rm "$1"/.INSTALL
busybox rm "$1"/.MTREE
busybox rm "$1"/.PKGINFO
'''

  with open('download.sh', 'w') as f:
    f.write(download_script)

  with open('unpack.sh', 'w') as f:
    f.write(unpack_script)

  with open('README.txt', 'w') as f:
    f.write('''INSTRUCTIONS
The target system will require a working copy of the busybox executable along
with the generated scripts. After running the scripts on the target system, the
pacman binary should work. To complete the recovery, re-install all of the
previously installed packages.

Make sure that the target system is the same architecture as the system that was
used to generate the scripts.

Download the necessary packages on the target system:

    busybox sh download.sh

Install the packages on the target system:

    busybox sh unpack.sh /

The previous command needs to be run as root. If the busybox binary is not suid
then this may not be possible.

If the scripts are being run from a live recovery system then replace "/" in the
prevous command with the path to the target system's root directory. This should
only be done if you do not have access to a live system with Pacman. If you do,
use Pacman to recover the target system instead.

WARNING
Directly unpacking package archives with "busybox tar" will overwrite
configuration files. Back up your files (if possible) before running the unpack
script.

DISCLAIMER
This code is untested and may not work. Use at your own risk.
''')

if __name__ == '__main__':
  try:
    main()
  except (KeyboardInterrupt, BrokenPipeError):
    pass
