예제 #1
0
def check_for_notmuch_database(email_archive_folder):
    notmuch_database_folder = email_archive_folder / Path(
        "_Maildirs/.notmuch/xapian")
    if not os.path.isdir(notmuch_database_folder):
        eprint(
            '''Error: notmuch has not created the xapian database yet. Run \"mail_update [email protected] --update\" first. Exiting.'''
        )
        sys.exit(1)
예제 #2
0
def create_root_device(
    ctx,
    devices: Tuple[Path, ...],
    partition_table: str,
    filesystem: str,
    force: bool,
    raid: str,
    raid_group_size: int,
    verbose: int,
    verbose_inf: bool,
    pool_name: str,
):

    devices = tuple([Path(_device) for _device in devices])

    eprint("installing gentoo on root devices:",
           ' '.join([_device.as_posix()
                     for _device in devices]), '(' + partition_table + ')',
           '(' + filesystem + ')', '(', pool_name, ')')
    for _device in devices:
        if not _device.name.startswith('nvme'):
            assert not _device.name[-1].isdigit()
        assert path_is_block_special(_device)
        assert not block_special_path_is_mounted(
            _device,
            verbose=verbose,
        )

    if len(devices) == 1:
        assert raid == 'disk'
    else:
        assert raid != 'disk'

    if not force:
        warn(
            devices,
            verbose=verbose,
        )

    if pool_name:
        ctx.invoke(
            write_sysfs_partition,
            devices=devices,
            force=True,
            filesystem=filesystem,
            raid=raid,
            pool_name=pool_name,
            raid_group_size=raid_group_size,
        )
    else:
        ctx.invoke(
            write_sysfs_partition,
            devices=devices,
            force=True,
            filesystem=filesystem,
            raid=raid,
            raid_group_size=raid_group_size,
        )
예제 #3
0
def check_noupdate_list(
    *,
    gpgmda_config_folder: Path,
    email_address: str,
    verbose: bool,
    debug: bool,
):
    noupdate_list = open(gpgmda_config_folder / Path(".noupdate"),
                         'r').readlines()  # todo move config to ~/.gpgmda
    for item in noupdate_list:
        if email_address in item:
            eprint(email_address + " is listed in .noupdate, exiting")
            sys.exit(1)
예제 #4
0
def install(ctx, *,
            root_devices: Tuple[Path, ...],
            vm: str,
            vm_ram: int,
            boot_device: Path,
            boot_device_partition_table: str,
            root_device_partition_table: str,
            boot_filesystem: str,
            root_filesystem: str,
            stdlib: str,
            arch: str,
            raid: str,
            raid_group_size: int,
            march: str,
            hostname: str,
            newpasswd: str,
            ip: str,
            ip_gateway: str,
            mesa_use_enable: list[str],
            mesa_use_disable: list[str],
            proxy: str,
            force: bool,
            encrypt: bool,
            pinebook_overlay: bool,
            kernel: str,
            multilib: bool,
            minimal: bool,
            verbose: int,
            verbose_inf: bool,
            skip_to_chroot: bool,
            ):

    assert isinstance(root_devices, tuple)
    boot_device = Path(boot_device)
    root_devices = tuple([Path(_device) for _device in root_devices])
    assert isinstance(root_devices, tuple)
    assert hostname.lower() == hostname
    assert '_' not in hostname

    mount_path = Path("/mnt/gentoo")
    mount_path_boot = mount_path / Path('boot')
    mount_path_boot_efi = mount_path_boot / Path('efi')

    if not skip_to_chroot:
        distfiles_dir = Path('/var/db/repos/gentoo/distfiles')
        os.makedirs(distfiles_dir, exist_ok=True)

        if not os.path.isdir('/var/db/repos/gentoo/sys-kernel'):
            eprint("run emerge --sync first")
            sys.exit(1)
        if encrypt:
            eprint("encryption not yet supported")
            #sys.exit(1)
        if stdlib == 'uclibc':
            eprint("uclibc fails with efi grub because efivar fails to compile. See Note.")
            sys.exit(1)

        os.makedirs(mount_path, exist_ok=True)

        assert Path('/usr/bin/ischroot').exists()
        eprint("using C library:", stdlib)
        eprint("hostname:", hostname)

        if root_filesystem == '9p':
            assert vm

        if vm:
            assert vm_ram
            assert root_filesystem == "9p"
            assert not root_devices
            assert not boot_device
            assert not boot_filesystem
            guests_root = Path('/guests') / Path(vm)
            guest_path        = guests_root / Path(hostname)
            guest_path_chroot = guests_root / Path(hostname + "-chroot")
            os.makedirs(guest_path, exist_ok=True)
            os.makedirs(guest_path_chroot, exist_ok=True)
            mount_path = guest_path
        else:
            assert boot_device
            assert root_devices

        if boot_device:
            assert boot_device_partition_table
            assert boot_filesystem

        if root_devices:
            assert root_device_partition_table

        if len(root_devices) > 1:
            assert root_filesystem == 'zfs'
        elif len(root_devices) == 1:
            if root_filesystem == 'zfs':
                assert raid == 'disk'

        if root_filesystem == 'zfs':
            assert root_device_partition_table == 'gpt'

        if root_filesystem == 'zfs' or boot_filesystem == 'zfs':
            input("note zfs boot/root is not working, many fixes will be needed, press enter to break things")

        safety_check_devices(boot_device=boot_device,
                             root_devices=root_devices,
                             verbose=verbose,
                             boot_device_partition_table=boot_device_partition_table,
                             boot_filesystem=boot_filesystem,
                             root_device_partition_table=root_device_partition_table,
                             root_filesystem=root_filesystem,
                             force=force,)

        if boot_device and root_devices and not vm:
            if boot_device == root_devices[0]:
                assert boot_filesystem == root_filesystem
                assert boot_device_partition_table == root_device_partition_table
                if boot_filesystem == 'zfs':
                    assert False
                    # untested
                    #ctx.invoke(destroy_block_devices_head_and_tail,
                    #           devices=root_devices,
                    #           force=True,
                    #           no_backup=True,
                    #           size=(1024 * 1024 * 128),
                    #           note=False,
                    #           verbose=verbose,
                    #           )
                    ## if this is zfs, it will make a gpt table, / and EFI partition
                    #ctx.invoke(create_root_device,
                    #           devices=root_devices,
                    #           exclusive=True,
                    #           filesystem=root_filesystem,
                    #           partition_table=root_device_partition_table,
                    #           force=True,
                    #           raid=raid,
                    #           raid_group_size=raid_group_size,
                    #           pool_name=hostname,)
                    #create_boot_device(ctx,
                    #                   device=boot_device,
                    #                   partition_table='none',
                    #                   filesystem=boot_filesystem,
                    #                   force=True,
                    #                   verbose=verbose,
                    #                   )  # dont want to delete the gpt that zfs made
                    #boot_mount_command = False
                    #root_mount_command = False

                elif boot_filesystem == 'ext4':
                    ctx.invoke(destroy_block_device_head_and_tail,
                               device=boot_device,
                               force=True,)
                    create_boot_device(ctx,
                                       device=boot_device,
                                       partition_table=boot_device_partition_table,
                                       filesystem=boot_filesystem,
                                       force=True,
                                       verbose=verbose,
                                       )  # writes gurb_bios from 48s to 1023s then writes EFI partition from 1024s to 205824s (100M efi) (nope, too big for fat16)
                    ctx.invoke(create_root_device,
                               devices=root_devices,
                               filesystem=root_filesystem,
                               partition_table=root_device_partition_table,
                               force=True,
                               raid=raid,
                               raid_group_size=raid_group_size,
                               pool_name=hostname,)
                    root_partition_path = add_partition_number_to_device(device=root_devices[0], partition_number="3", verbose=verbose,)
                    root_mount_command = "mount " + root_partition_path.as_posix() + " " + str(mount_path)
                    boot_mount_command = False
                else:  # unknown case
                    assert False
            else:
                assert False
                # not tested
                #eprint("differing root and boot devices: (exclusive) root_devices[0]:", root_devices[0], "boot_device:", boot_device)
                #create_boot_device(ctx,
                #                   device=boot_device,
                #                   partition_table=boot_device_partition_table,
                #                   filesystem=boot_filesystem,
                #                   force=True,
                #                   verbose=verbose,
                #                   )
                #ctx.invoke(write_boot_partition,
                #           device=boot_device,
                #           force=True,
                #           verbose=verbose,
                #           )
                #ctx.invoke(create_root_device,
                #           devices=root_devices,
                #           exclusive=True,
                #           filesystem=root_filesystem,
                #           partition_table=root_device_partition_table,
                #           force=True,
                #           raid=raid,)
                #if root_filesystem == 'zfs':
                #    assert False
                #    root_mount_command = False
                #elif root_filesystem == 'ext4':
                #    root_partition_path = add_partition_number_to_device(device=root_devices[0], partition_number="1")
                #    root_mount_command = "mount " + root_partition_path.as_posix() + " " + mount_path.as_posix()

                #boot_partition_path = add_partition_number_to_device(device=boot_device, partition_number="3")
                #boot_mount_command = "mount " + boot_partition_path.as_posix() + " " + mount_path_boot.as_posix()

            if root_mount_command:
                run_command(root_mount_command, verbose=verbose,)

            assert path_is_mounted(mount_path, verbose=verbose,)

            os.makedirs(mount_path_boot, exist_ok=True)

            if boot_mount_command:
                run_command(boot_mount_command, verbose=verbose,)
                assert path_is_mounted(mount_path_boot, verbose=verbose,)
            else:
                assert not path_is_mounted(mount_path_boot, verbose=verbose,)

            if boot_device:
                os.makedirs(mount_path_boot_efi, exist_ok=True)

            if boot_filesystem == 'zfs':
                efi_partition_path = add_partition_number_to_device(device=boot_device, partition_number="9", verbose=verbose,)
                efi_mount_command = "mount " + efi_partition_path.as_posix() + " " + mount_path_boot_efi.as_posix()
            else:
                efi_partition_path = add_partition_number_to_device(device=boot_device, partition_number="2", verbose=verbose,)
                efi_mount_command = "mount " + efi_partition_path.as_posix() + " " + mount_path_boot_efi.as_posix()

            if boot_device:
                run_command(efi_mount_command, verbose=verbose,)
                assert path_is_mounted(mount_path_boot_efi, verbose=verbose,)

        extract_stage3(stdlib=stdlib,
                       multilib=multilib,
                       arch=arch,
                       destination=mount_path,
                       expect_mounted_destination=True,
                       vm=vm,
                       vm_ram=vm_ram,
                       verbose=verbose,
                       )

    # skip_to_chroot lands here
    if not boot_device:
        assert False
        #boot_device = "False"  # fixme

    skip_to_rsync = False
    if skip_to_chroot:
        skip_to_rsync = True

    ctx.invoke(chroot_gentoo,
               mount_path=mount_path,
               stdlib=stdlib,
               boot_device=boot_device,
               hostname=hostname,
               march=march,
               arch=arch,
               root_filesystem=root_filesystem,
               newpasswd=newpasswd,
               kernel=kernel,
               ip=ip,
               ip_gateway=ip_gateway,
               vm=vm,
               mesa_use_enable=mesa_use_enable,
               mesa_use_disable=mesa_use_disable,
               pinebook_overlay=pinebook_overlay,
               ipython=False,
               skip_to_rsync=skip_to_rsync,
               verbose=verbose,
               )
예제 #5
0
def write_sysfs_partition(
    ctx,
    devices: Tuple[Path, ...],
    filesystem: str,
    force: bool,
    exclusive: bool,
    raid: str,
    raid_group_size: int,
    pool_name: str,
    verbose: int,
    verbose_inf: bool,
):

    tty, verbose = tv(
        ctx=ctx,
        verbose=verbose,
        verbose_inf=verbose_inf,
    )

    devices = tuple([Path(_device) for _device in devices])
    ic('creating sysfs partition on:', devices)

    if filesystem == 'zfs':
        assert pool_name

    for device in devices:
        if not device.name.startswith('nvme'):
            assert not device.name[-1].isdigit()
        assert path_is_block_special(device)
        assert not block_special_path_is_mounted(
            device,
            verbose=verbose,
        )

    if not force:
        warn(
            devices,
            verbose=verbose,
        )

    if filesystem in ['ext4', 'fat32']:
        assert len(devices) == 1
        if exclusive:
            #destroy_block_device_head_and_tail(device=device, force=True) #these are done in create_root_device
            #write_gpt(device)
            partition_number = '1'
            start = "0%"
            end = "100%"
        else:
            partition_number = '3'
            start = "100MiB"
            end = "100%"

        run_command("parted -a optimal " + devices[0].as_posix() +
                    " --script -- mkpart primary " + filesystem + ' ' + start +
                    ' ' + end,
                    verbose=verbose)
        run_command(
            "parted  " + devices[0].as_posix() + " --script -- name " +
            partition_number + " rootfs",
            verbose=verbose,
        )
        time.sleep(1)
        sysfs_partition_path = add_partition_number_to_device(
            device=devices[0],
            partition_number=partition_number,
            verbose=verbose,
        )
        if filesystem == 'ext4':
            ext4_command = sh.Command('mkfs.ext4')
            ext4_command(sysfs_partition_path.as_posix(),
                         _out=sys.stdout,
                         _err=sys.stderr)
        elif filesystem == 'fat32':
            mkfs_vfat_command = sh.Command('mkfs.vfat',
                                           sysfs_partition_path.as_posix())
            mkfs_vfat_command(_out=sys.stdout, _err=sys.stderr)
        else:
            eprint("unknown filesystem:", filesystem)
            sys.exit(1)

    elif filesystem == 'zfs':
        assert exclusive
        assert False
        ctx.invoke(
            write_zfs_root_filesystem_on_devices,
            ctx=ctx,
            devices=devices,
            mount_point=None,
            force=True,
            raid=raid,
            raid_group_size=raid_group_size,
            pool_name=pool_name,
            verbose=verbose,
        )
    else:
        eprint("unknown filesystem:", filesystem)
        sys.exit(1)
예제 #6
0
def run_notmuch(
    *,
    mode,
    email_address,
    email_archive_folder,
    gpgmaildir,
    query,
    notmuch_config_file,
    notmuch_config_folder,
):

    ic()
    yesall = False

    if mode == "update_notmuch_db":
        current_env = os.environ.copy()
        current_env["NOTMUCH_CONFIG"] = notmuch_config_file

        notmuch_new_command = [
            "notmuch", "--config=" + notmuch_config_file.as_posix(), "new"
        ]
        ic(notmuch_new_command)
        notmuch_p = subprocess.Popen(notmuch_new_command,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE,
                                     shell=False,
                                     env=current_env)
        ic(notmuch_p.args)
        notmuch_p_output = notmuch_p.communicate()

        ic('notmuch_p_output:')
        ic(notmuch_p_output)

        ic('len(notmuch_p_output[0]):', len(notmuch_p_output[0]))

        ic('notmuch_p_output[0]:')
        for line in notmuch_p_output[0].split(b'\n'):
            ic(line.decode('utf-8'))

        ic('notmuch_p_output[1]:')
        for line in notmuch_p_output[1].decode('utf8').split('\n'):
            #line = line.decode('utf-8')
            ic(line)
            if "Note: Ignoring non-mail file:" in line:
                non_mail_file = Path(line.split(" ")[-1])
                ic('found file that gmime does not like:', non_mail_file)
                random_id = Path(non_mail_file.as_posix()[-40:])
                ic(random_id)
                #maildir_subfolder = Path(non_mail_file.parent.parent)
                maildir_subfolder = Path(non_mail_file.parent).name
                ic(maildir_subfolder)
                assert maildir_subfolder in ['new', '.sent']
                encrypted_file = Path(gpgmaildir) / Path(
                    maildir_subfolder) / Path(random_id)
                ic(encrypted_file)
                ic('head -c 500:')
                command = "head -c 500 " + non_mail_file.as_posix()
                os.system(command)
                with open(non_mail_file, 'rb') as fh:
                    data = fh.read()
                if data == b'metastable':
                    ic('metastable test message... fixme')

                if not yesall:

                    #ic('running vi')
                    #command = "vi " + non_mail_file
                    #os.system(command)

                    delete_message_answer = \
                        input("Would you like to move this message locally to the ~/.gpgmda/non-mail folder and delete it on the server? (yes/no/skipall/yesall): ")

                    if delete_message_answer.lower() == "yesall":
                        yesall = True
                    if delete_message_answer.lower() == "skipall":
                        break
                else:
                    delete_message_answer = 'yes'

                if delete_message_answer.lower() == "yes":
                    non_mail_path = Path('~/.gpgmda/non-mail').expanduser()

                    os.makedirs(non_mail_path, exist_ok=True)

                    ic('Processing files for local move and delete:')

                    ic(non_mail_file)
                    sh.mv(non_mail_file, non_mail_path)

                    ic(encrypted_file)
                    sh.mv(encrypted_file, non_mail_path)

                    if maildir_subfolder == ".sent":
                        target_file = Path(
                            "/home/sentuser/gpgMaildir/new/") / random_id
                        command = "ssh [email protected] rm -v " + target_file.as_posix(
                        )
                        ic(command)
                        os.system(command)

                    elif maildir_subfolder == "new":
                        target_file = Path(
                            "/home/user/gpgMaildir/new/") / random_id
                        command = "ssh [email protected] rm -v " + target_file.as_posix(
                        )  # todo use ~/.gpgmda/config
                        ic(command)
                        os.system(command)
                    else:
                        eprint("unknown maildir_subfolder:",
                               maildir_subfolder.as_posix(), "exiting")
                        sys.exit(1)

        ic(notmuch_p.returncode)
        if notmuch_p.returncode != 0:
            eprint("notmuch new did not return 0, exiting")
            sys.exit(1)

    elif mode == "query_notmuch":
        check_for_notmuch_database(email_archive_folder=email_archive_folder)
        command = "NOTMUCH_CONFIG=" + notmuch_config_file.as_posix(
        ) + " notmuch " + query
        ic(command)
        return_code = os.system(command)
        if return_code != 0:
            eprint("\"notmuch " + query + "\" returned nonzero, exiting")
            sys.exit(1)

    elif mode == "query_afew":
        check_for_notmuch_database(email_archive_folder=email_archive_folder)
        command = "afew" + " --notmuch-config=" + notmuch_config_file.as_posix(
        ) + " " + query
        ic(command)
        return_code = os.system(command)
        if return_code != 0:
            eprint("\"notmuch " + query + "\" returned nonzero, exiting")
            sys.exit(1)

    elif mode == "query_address_db":
        check_for_notmuch_database(email_archive_folder=email_archive_folder)
        command = "XDG_CONFIG_HOME=" + \
                  notmuch_config_folder.as_posix() + \
                  " NOTMUCH_CONFIG=" + \
                  notmuch_config_file.as_posix() + " " + \
                  "nottoomuch-addresses.sh " + \
                  query
        return_code = os.system(command)
        if return_code != 0:
            eprint("\"nottoomuch-addresses.sh\" returned nonzero, exiting")
            sys.exit(1)

    elif mode == "build_address_db":
        check_for_notmuch_database(email_archive_folder=email_archive_folder)
        command = "XDG_CONFIG_HOME=" + \
                  notmuch_config_folder.as_posix() + \
                  " NOTMUCH_CONFIG=" + \
                  notmuch_config_file.as_posix() + \
                  " " + \
                  "nottoomuch-addresses.sh --update --rebuild"
        return_code = os.system(command)
        if return_code != 0:
            eprint("\"nottoomuch-addresses.sh\" returned nonzero, exiting")
            sys.exit(1)

    elif mode == "update_address_db":
        check_for_notmuch_database(email_archive_folder=email_archive_folder)
        command = "XDG_CONFIG_HOME=" + \
                  notmuch_config_folder.as_posix() + \
                  " NOTMUCH_CONFIG=" + \
                  notmuch_config_file.as_posix() + " " + \
                  "nottoomuch-addresses.sh --update"
        return_code = os.system(command)
        if return_code != 0:
            eprint("\"nottoomuch-addresses.sh\" returned nonzero, exiting")
            sys.exit(1)

    else:
        ic('invalid', mode, 'exiting.')
        sys.exit(1)
예제 #7
0
def gpgmaildir_to_maildir(
    *,
    email_address: str,
    gpgMaildir_archive_folder: Path,
    gpgmaildir: Path,
    maildir: Path,
    verbose: bool,
    debug: bool,
):

    # todo add locking
    ic()
    assert isinstance(maildir, Path)
    assert isinstance(gpgmaildir, Path)
    assert isinstance(gpgMaildir_archive_folder, Path)
    iterator = None
    ic('gpgmda_to_maildir using:', gpgMaildir_archive_folder)
    ic('Checking for default-recipient in ~/.gnupg/gpg.conf')
    command = "grep \"^default-recipient\" ~/.gnupg/gpg.conf"
    grep_exit_code = os.system(command)
    if grep_exit_code != 0:
        eprint(
            "error: default-recipient is not defined in ~/.gnupg/gpg.conf. Exiting."
        )
        sys.exit(1)

    rsync_last_new_mail_file = '/dev/shm/.gpgmda_rsync_last_new_mail_' + email_address
    ic('checking to see if', rsync_last_new_mail_file,
       'exists and is greater than 0 bytes')
    rsync_files_transferred = 0
    if path_exists(rsync_last_new_mail_file):
        with open(rsync_last_new_mail_file, 'r') as fh:
            for line in fh.readlines():
                if 'Number of regular files transferred:' in line:
                    ic(line)
                    rsync_files_transferred = int(line.split(':')[1].strip())
                    ic(rsync_files_transferred)
                    break
        if rsync_files_transferred == 0:
            ic('rsync transferred 0 files, skipping decrypt')

        else:
            rsync_list = parse_rsync_log_to_list(
                email_address=email_address,
                gpgMaildir_archive_folder=gpgMaildir_archive_folder)
            ic(rsync_list)
            iterator = rsync_list
            skip_hashes = []

    else:
        ic(rsync_last_new_mail_file, 'does not exist or is 0 bytes')

    ic('checking if the message counts in the maildir and the gpgmaildir match'
       )
    maildir_counts_dict = get_maildir_file_counts(
        gpgmaildir=gpgmaildir,
        maildir=maildir,
        verbose=verbose,
        debug=debug,
    )
    ic(maildir_counts_dict)
    maildir_file_count = maildir_counts_dict['files_in_maildir']
    gpgmaildir_file_count = maildir_counts_dict['files_in_gpgmaildir']

    ##if gpgmaildir_file_count > maildir_file_count:  # not a good check.
    #if gpgmaildir_file_count != maildir_file_count:  # not a good check.
    #    ic('files_in_gpgmaildir != files_in_maildir:', gpgmaildir_file_count, '!=', maildir_file_count)
    #    ic('locating un-decrypted files')
    #    files_in_gpgmaildir = [dent.pathlib for dent in files(gpgmaildir, verbose=verbose, debug=debug,)]
    #    assert isinstance(files_in_gpgmaildir[0], Path)
    #    files_in_maildir = [dent.pathlib for dent in files(maildir, verbose=verbose, debug=debug,)]
    #    ic(len(files_in_gpgmaildir))
    #    ic(len(files_in_maildir))
    #    ic(len(files_in_gpgmaildir) - len(files_in_maildir))
    #    ic('building hash lists')
    #    #hashes_in_gpgmaildir = [path.name.split('.')[-1] for path in files_in_gpgmaildir]
    #    hashes_in_maildir = [path.name.split('.')[-1] for path in files_in_maildir]
    #    ic(len(hashes_in_maildir))
    #    skip_hashes = hashes_in_maildir
    #    iterator = files_in_gpgmaildir

    if iterator:  # used in 2 places above
        decrypt_list_of_messages(
            message_list=iterator,
            skip_hashes=skip_hashes,
            email_address=email_address,
            maildir=maildir,
            verbose=verbose,
            debug=debug,
        )
예제 #8
0
파일: newapp.py 프로젝트: jakeogh/newapp
def new(ctx,
        language: str,
        repo_url: str,
        group: str,
        branch: str,
        rename: Optional[str],
        templates: Optional[tuple[Path]],
        apps_folder: str,
        gentoo_overlay_repo: str,
        github_user: str,
        license: str,
        owner: str,
        owner_email: str,
        description: str,
        local: bool,
        use_existing_repo: bool,
        verbose: int,
        verbose_inf: bool,
        hg: bool,
        ):

    not_root()
    tty, verbose = tv(ctx=ctx,
                      verbose=verbose,
                      verbose_inf=verbose_inf,
                      )

    apps_folder = Path(apps_folder)
    ic(apps_folder)

    if templates:
        templates = [t.resolve() for t in templates]

    if repo_url.endswith('.git'):
        repo_url = repo_url[:-4]

    assert '/' not in group
    assert ':' not in group
    assert group in portage_categories()
    assert repo_url.startswith('https://')

    template_repo_url: Optional[str] = None
    if not repo_url.startswith('https://github.com/{}/'.format(github_user)):
        template_repo_url = repo_url
        _app_name, _app_user, _app_module_name, _app_path = parse_url(repo_url,
                                                                      apps_folder=apps_folder,
                                                                      verbose=verbose,
                                                                      )
        if rename:
            _app_name = rename
        repo_url = 'https://github.com/{github_user}/{app_name}'.format(github_user=github_user, app_name=_app_name)
        del _app_name, _app_user, _app_module_name, _app_path
    else:
        template_repo_url = None

    app_name, app_user, app_module_name, app_path = parse_url(repo_url,
                                                              apps_folder=apps_folder,
                                                              verbose=verbose,
                                                              )
    ic(app_name)
    ic(app_user)
    assert app_user == github_user
    assert '_' not in app_path.name

    if language == 'sh':
        language = 'bash'

    if language == 'python':
        ext = '.py'
    elif language == 'bash':
        ext = '.sh'
    elif language == 'zig':
        ext = '.zig'
        assert group == 'dev-zig'
    elif language == 'c':
        ext = '.c'
    else:
        raise ValueError('unsupported language: ' + language)

    if template_repo_url:
        clone_repo(repo_url=repo_url,
                   template_repo_url=template_repo_url,
                   apps_folder=apps_folder,
                   hg=hg,
                   branch=branch,
                   app_path=app_path,
                   app_group=group,
                   local=local,
                   verbose=verbose,
                   )

    if ((not app_path.exists()) or use_existing_repo):
        if not use_existing_repo:
            create_repo(hg=hg,
                        app_path=app_path,
                        app_module_name=app_module_name,
                        verbose=verbose,
                        )
        else:
            assert app_path.is_dir()
            assert Path(app_path / Path('.git')).exists()
            with chdir(app_path):
                os.makedirs(app_module_name, exist_ok=True)

        if not template_repo_url:
            with chdir(app_path):
                if language == 'python':
                    write_setup_py(use_existing_repo=use_existing_repo,
                                   app_module_name=app_module_name,
                                   app_name=app_name,
                                   owner=owner,
                                   owner_email=owner_email,
                                   description=description,
                                   license=license,
                                   repo_url=repo_url,)

                gitignore_template = generate_gitignore_template()
                if use_existing_repo:
                    with open('.gitignore', 'a') as fh:
                        fh.write(gitignore_template)
                else:
                    with open('.gitignore', 'x') as fh:
                        fh.write(gitignore_template)

                if not Path('url.sh').exists():
                    write_url_sh(repo_url, verbose=verbose,)

                if language == 'python':
                    os.system("fastep")

            with chdir(app_path / app_module_name):
                app_template = generate_app_template(package_name=app_module_name,
                                                     language=language,
                                                     append_files=templates,
                                                     verbose=ctx.obj['verbose'],
                                                     )
                with open(app_module_name + ext, 'x') as fh:
                    fh.write(app_template)

                if language == 'python':
                    init_template = generate_init_template(package_name=app_module_name)
                    with open("__init__.py", 'x') as fh:
                        fh.write(init_template)
                    sh.touch('py.typed')

            with chdir(app_path):
                sh.git.add('--all')
                sh.git.commit('-m', 'autocomit')

        with chdir(app_path):
            with open(".edit_config", 'x') as fh:
                fh.write(generate_edit_config(package_name=app_name,
                                              package_group=group,
                                              local=local))

            remote_add_origin(hg=hg,
                              app_path=app_path,
                              local=local,
                              app_name=app_name,
                              verbose=verbose,
                              )
    else:
        eprint("Not creating new app, {} already exists.".format(app_path))


    ebuild_path = Path(gentoo_overlay_repo) / Path(group) / Path(app_name)
    if not ebuild_path.exists():
        enable_python = False
        if Path(app_path / Path('setup.py')).exists():
            enable_python = True

        os.makedirs(ebuild_path, exist_ok=False)
        ebuild_name = app_name + "-9999.ebuild"

        enable_dobin = False
        if language in ['bash']:
            enable_dobin = True
        with chdir(ebuild_path):
            with open(ebuild_name, 'w') as fh:
                fh.write(generate_ebuild_template(app_name=app_name,
                                                  description=description,
                                                  enable_python=enable_python,
                                                  enable_dobin=enable_dobin,
                                                  homepage=repo_url,
                                                  app_path=app_path,))
            sh.git.add(ebuild_name)
            sh.ebuild(ebuild_name,  'manifest')
            sh.git.add('*')
            os.system("git commit -m 'newapp {}'".format(app_name))
            os.system("git push")
            os.system("sudo emaint sync -A")
            accept_keyword = "={}/{}-9999 **\n".format(group, app_name)
            accept_keywords = Path("/etc/portage/package.accept_keywords") / Path(group) / Path(app_name)
            accept_keywords.parent.mkdir(exist_ok=True)
            write_line_to_file(path=accept_keywords,
                               line=accept_keyword,
                               unique=True,
                               make_new_if_necessary=True,
                               verbose=verbose,
                               )
            sh.ln('-s', ebuild_path / ebuild_name, app_path / ebuild_name)
            sh.git.diff('--exit-code')
            # need to commit any pending ebuild changes here, but that's the wront git message, and it fails if it's unhanged
            sh.git.commit('-m', 'initial commit', _ok_code=[0, 1])
    else:
        eprint('Not creating new ebuild, {} already exists.'.format(ebuild_path))

    ic(app_path)
    ic(app_module_name)

    main_py_path = app_path / Path(app_module_name) / Path(app_module_name + ext)
    ic(main_py_path)
    os.system("edittool edit " + main_py_path.as_posix())