#!/usr/bin/python3
# -*- coding: utf-8 -*-

import os
import subprocess

message_printed = False

def print_message():
    # Print user-friendly message
    global message_printed
    if not message_printed:
        print()
        print('Bylo detekováno zvětšení virtuálního disku.')
        print('Systém nyní upraví nastavení diskových oddílů.')
        print()
        message_printed = True

def get_sizes():
    # Returns device sizes in MB
    sizes = {}
    lsblk = subprocess.run(['/bin/lsblk', '-bnro', 'PATH,SIZE'], capture_output=True, check=True).stdout.decode()
    for line in lsblk.splitlines():
        line = line.split()
        sizes[line[0]] = int(line[1])//1024**2
    return sizes

# Determine partition and disk paths
with open('/etc/crypttab') as f:
    crypttab = dict(line.split()[:2] for line in f.read().splitlines())
partition = subprocess.run(['/sbin/findfs', crypttab['system']], capture_output=True, check=True).stdout.decode().strip()
disk = partition.rstrip('0123456789')

# Resize physical partition when there is more than 10M unallocated
sizes = get_sizes()
free_space = sizes[disk] - sum([size for part,size in sizes.items() if part.startswith(disk) and part[len(disk):].isdigit()])
if free_space > 10:
    print_message()
    try:
        # Force busybox fdisk as util-linux fdisk breaks the subsequent partx command by notifying kernel too hard
        input = 'd\n2\nn\np\n2\n\n\nt\n2\n8e\nw'.encode()
        subprocess.run(['/bin/busybox', 'fdisk', disk], input=input, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
    except subprocess.CalledProcessError as e:
        # Returns code 1  because of the nofication failure, but the resize is successful anyway
        if e.returncode != 1:
            raise
    # Re-read partition (partx is from util-linux, issues BLKPG_RESIZE_PARTITION ioctl to update the kernel partition table)
    subprocess.run(['/usr/sbin/partx', '-u', partition], stdout=subprocess.DEVNULL, check=True)
    sizes = get_sizes()

# Resize dmcrypt when there is more than 10M of free space (16M is consumed by dmcrypt)
free_space = sizes[partition] - sizes['/dev/mapper/system'] - 16
if free_space > 10:
    print_message()
    # Create locking dir for cryptsetup to suppress warning
    try:
        os.mkdir('/run/cryptsetup', 0o700)
    except FileExistsError:
        pass
    # Ask for password 3 times, then fail
    fails = 0
    while True:
        try:
            subprocess.run(['/sbin/cryptsetup', 'resize', 'system'], check=True)
            break
        except (subprocess.CalledProcessError, KeyboardInterrupt):
            fails += 1
            if fails == 3:
                raise
    sizes = get_sizes()

# Resize LVM PV when there is more than 10M of free space
free_space = sizes['/dev/mapper/system'] - sum(size for part,size in sizes.items() if part.startswith('/dev/mapper/vg0'))
if free_space > 10:
    print_message()
    subprocess.run(['/sbin/pvresize', '/dev/mapper/system'], stdout=subprocess.DEVNULL, check=True)
# Create swap if it doesn't exist yet and there's at least 4 GB of free space
if '/dev/mapper/vg0-swap' not in sizes and free_space > 4*1024:
    subprocess.run(['/sbin/lvcreate', '-L', '4G', 'vg0', '-n', 'swap'], stdout=subprocess.DEVNULL, check=True)
    subprocess.run(['/sbin/mkswap', '/dev/mapper/vg0-swap'], stdout=subprocess.DEVNULL, check=True)
    subprocess.run(['/sbin/swapon', '/dev/mapper/vg0-swap'], stdout=subprocess.DEVNULL, check=True)
    sizes = get_sizes()
    free_space = sizes['/dev/mapper/system'] - sum(size for part,size in sizes.items() if part.startswith('/dev/mapper/vg0'))
# Resize LVM LV when there is more than 10M of free space
if free_space > 10:
    subprocess.run(['/sbin/lvextend', '-l', '+100%FREE', '/dev/mapper/vg0-root'], stdout=subprocess.DEVNULL, check=True)
    subprocess.run(['/usr/sbin/resize2fs', '/dev/mapper/vg0-root'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
