"use strict";
/**
 * penguins-eggs
 * krill modules: krill-prepare.ts
 * author: Piero Proietti
 * email: piero.proietti@gmail.com
 * license: MIT
 * https://stackoverflow.com/questions/23876782/how-do-i-split-a-typescript-class-into-multiple-files
 */
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
/** OEM Installation (https://github.com/calamares/calamares/issues/871)
 *
 * Thanks to Adriaan De Groot (calamares author)
 *
 * There are two phases involved here, both of which could confusingly be called "OEM mode".
 * To be clear, the goal of the two phases is to end up with Linux (some distro) installed
 * on a piece of hardware which is passed to a customer's hands. On first boot, the
 * hardware is (re)configured with user input.
 *
 * Phase 1, OEM preparation
 * is about creating the image that will be used on first boot; so that is a normal partition,
 * create a standard user (e.g. "live") with autologin, populate a default desktop with a big
 * icon "first run this" or set up autostart of the configurator.
 * It is roughly a standard installation, with a little more configuration of the desktop
 * of the live user.
 *
 *   - welcome    - no need
 *   - locale     - en_US.UTF-8
 *   - keyboard   - us
 *   - partition  - ext4
 *   - users      - live/evolution root/evolution
 *   - network    - dhcp
 *   - summary    - no need
 *
 * Phase 2, "OEM user"
 * Set User Information on first Boot, from the "live" user, a new user for actual login --
 * along with whatever other configurations are wanted for the distro for the actual user.
 *
 *   - welcome
 *   - locale
 *   - keyboard
 *   - partition  - no need
 *   - users
 *   - network
 *   - summary
 *
 * Phase one should allow an empty password for the live user, and it would be useful to read
 * configuration from a file instead of having to go through the UI. It should install a
 * phase-2-configured Calamares into the target system
 * (either through a package, or as part of the image).
 *
 * Phase two is possible with the "dont-chroot" flag and (again) careful configuration.
 * Typically you drop the partition module (that was done in phase 1) and keep the users
 * module, add in a delete-calamares script (note to downstreams: that kind of module
 * should be upstreamed), add some user configuration (e.g. Plasma LNF if you're a
 * KDE-shipping-distro). To get all that you just need Calamares installed and the
 * relevant /etc/calamares/ files.
 */
const os_1 = tslib_1.__importDefault(require("os"));
const react_1 = tslib_1.__importDefault(require("react"));
const ink_1 = require("ink");
const utils_1 = tslib_1.__importDefault(require("../classes/utils"));
const axios_1 = tslib_1.__importDefault(require("axios"));
const shelljs_1 = tslib_1.__importDefault(require("shelljs"));
const fs_1 = tslib_1.__importDefault(require("fs"));
const systemctl_1 = tslib_1.__importDefault(require("../classes/systemctl"));
const locales_1 = tslib_1.__importDefault(require("../classes/locales"));
const keyboards_1 = tslib_1.__importDefault(require("../classes/keyboards"));
// libraries
const exec = require('../lib/utils').exec;
const welcome_1 = tslib_1.__importDefault(require("../components/welcome"));
const location_1 = tslib_1.__importDefault(require("../components/location"));
const partitions_1 = tslib_1.__importDefault(require("../components/partitions"));
const keyboard_1 = tslib_1.__importDefault(require("../components/keyboard"));
const users_1 = tslib_1.__importDefault(require("../components/users"));
const network_1 = tslib_1.__importDefault(require("../components/network"));
const summary_1 = tslib_1.__importDefault(require("../components/summary"));
const select_languages_1 = tslib_1.__importDefault(require("../lib/select_languages"));
const select_regions_1 = tslib_1.__importDefault(require("../lib/select_regions"));
const select_zones_1 = tslib_1.__importDefault(require("../lib/select_zones"));
const select_installation_device_1 = tslib_1.__importDefault(require("../lib/select_installation_device"));
const select_installation_mode_1 = tslib_1.__importDefault(require("../lib/select_installation_mode"));
const select_user_swap_choice_1 = tslib_1.__importDefault(require("../lib/select_user_swap_choice"));
const select_filesystem_type_1 = tslib_1.__importDefault(require("../lib/select_filesystem_type"));
const get_username_1 = tslib_1.__importDefault(require("../lib/get_username"));
const get_userfullname_1 = tslib_1.__importDefault(require("../lib/get_userfullname"));
const get_hostname_1 = tslib_1.__importDefault(require("../lib/get_hostname"));
const get_password_1 = tslib_1.__importDefault(require("../lib/get_password"));
const select_keyboard_model_1 = tslib_1.__importDefault(require("../lib/select_keyboard_model"));
const select_keyboard_layout_1 = tslib_1.__importDefault(require("../lib/select_keyboard_layout"));
const select_keyboard_variant_1 = tslib_1.__importDefault(require("../lib/select_keyboard_variant"));
const select_keyboard_option_1 = tslib_1.__importDefault(require("../lib/select_keyboard_option"));
const select_interface_1 = tslib_1.__importDefault(require("../lib/select_interface"));
const select_address_type_1 = tslib_1.__importDefault(require("../lib/select_address_type"));
const get_address_1 = tslib_1.__importDefault(require("../lib/get_address"));
const get_netmask_1 = tslib_1.__importDefault(require("../lib/get_netmask"));
const get_gateway_1 = tslib_1.__importDefault(require("../lib/get_gateway"));
const get_domain_1 = tslib_1.__importDefault(require("../lib/get_domain"));
const get_dns_1 = tslib_1.__importDefault(require("../lib/get_dns"));
const krill_sequence_1 = tslib_1.__importDefault(require("./krill-sequence"));
const config_file = '/etc/penguins-eggs.d/krill.yaml';
/**
 *
 */
class Krill {
    /**
     * constructor
     * @param unattended
     * @param nointeractive
     * @param halt
     */
    constructor(unattended = false, nointeractive = false, halt = false) {
        this.krillConfig = {};
        this.locales = new locales_1.default();
        this.keyboards = new keyboards_1.default();
        this.unattended = false;
        this.nointeractive = false;
        this.halt = false;
        this.unattended = unattended;
        this.nointeractive = nointeractive;
        this.halt = halt;
    }
    /**
     *
     * @param krillConfig
     * @param ip
     * @param random
     * @param domain
     * @param suspend
     * @param small
     * @param none
     * @param cryped
     * @param pve
     * @param verbose
     */
    async prepare(krillConfig = {}, ip = false, random = false, domain = '', suspend = false, small = false, none = false, cryped = false, pve = false, verbose = false) {
        /**
         * Check for disk presence
         */
        const drives = shelljs_1.default.exec('lsblk |grep disk|cut -f 1 "-d "', { silent: true }).stdout.trim().split('\n');
        if (drives[0] === '') {
            await utils_1.default.pressKeyToExit(`No disk to install the system in this machine.\nkrill installer refuses to continue`);
        }
        /**
         * check for lvm2
         */
        if (await this.pvExist()) {
            await utils_1.default.pressKeyToExit(`There is a lvm2 volume in the system, remove it manually before installation.\nkrill installer refuses to continue`);
            process.exit();
        }
        /**
        * stop udisks2.service
        */
        const systemdCtl = new systemctl_1.default(verbose);
        if (await systemdCtl.isActive('udisks2.service')) {
            await systemdCtl.stop('udisks2.service');
        }
        let oWelcome = {};
        let oLocation = {};
        let oKeyboard = {};
        let oPartitions = {};
        let oUsers = {};
        let oNetwork = {};
        /**
         * load default values
         */
        if (!fs_1.default.existsSync(config_file)) {
            console.log(`cannot find configuration file ${config_file},`);
            process.exit(1);
        }
        this.krillConfig = krillConfig;
        oWelcome = { language: this.krillConfig.language };
        // Try to auto-configure timezone by internet
        const url = `https://geoip.kde.org/v1/calamares`;
        try {
            const response = await axios_1.default.get(url);
            if (response.statusText === 'OK') {
                const data = JSON.stringify(response.data);
                const obj = JSON.parse(data);
                this.krillConfig.region = obj.time_zone.substring(0, obj.time_zone.indexOf('/'));
                this.krillConfig.zone = obj.time_zone.substring(obj.time_zone.indexOf('/') + 1);
            }
        }
        catch (error) {
            console.error('error: ' + error);
        }
        oLocation = {
            language: this.krillConfig.language,
            region: this.krillConfig.region,
            zone: this.krillConfig.zone
        };
        oKeyboard = {
            keyboardModel: this.krillConfig.keyboardModel,
            keyboardLayout: this.krillConfig.keyboardLayout,
            keyboardVariant: this.krillConfig.keyboardVariant,
            keyboardOption: this.krillConfig.keyboardOption
        };
        const driveList = [];
        drives.forEach((element) => {
            driveList.push('/dev/' + element);
        });
        let installationDevice = driveList[0];
        if (driveList.length > 1) {
            installationDevice = await (0, select_installation_device_1.default)();
        }
        oPartitions = {
            installationDevice: installationDevice,
            installationMode: this.krillConfig.installationMode,
            filesystemType: this.krillConfig.filesystemType,
            userSwapChoice: this.krillConfig.userSwapChoice
        };
        if (suspend) {
            oPartitions.userSwapChoice = 'suspend';
        }
        else if (small) {
            oPartitions.userSwapChoice = 'small';
        }
        else if (none) {
            oPartitions.userSwapChoice = 'none';
        }
        let hostname = this.krillConfig.hostname;
        if (hostname === '') {
            hostname = shelljs_1.default.exec('cat /etc/hostname').trim();
        }
        if (ip) {
            hostname = 'ip-' + utils_1.default.address().replaceAll('.', '-');
        }
        if (random) {
            const fl = shelljs_1.default.exec(`tr -dc a-z </dev/urandom | head -c 2 ; echo ''`, { silent: true }).trim();
            const n = shelljs_1.default.exec(`tr -dc 0-9 </dev/urandom | head -c 3 ; echo ''`, { silent: true }).trim();
            const sl = shelljs_1.default.exec(`tr -dc a-z </dev/urandom | head -c 2 ; echo ''`, { silent: true }).trim();
            hostname = `${os_1.default.hostname()}-${fl}${n}${sl}`;
        }
        oUsers = {
            name: this.krillConfig.name,
            fullname: this.krillConfig.fullname,
            password: this.krillConfig.password,
            rootPassword: this.krillConfig.rootPassword,
            autologin: this.krillConfig.autologin,
            hostname: hostname
        };
        oNetwork =
            {
                iface: await utils_1.default.iface(),
                addressType: this.krillConfig.addressType,
                address: utils_1.default.address(),
                netmask: utils_1.default.netmask(),
                gateway: utils_1.default.gateway(),
                dns: utils_1.default.getDns(),
                domain: utils_1.default.getDomain()
            };
        /**
         * interactive
         */
        if (!this.unattended) {
            oWelcome = await this.welcome();
            oLocation = await this.location(oWelcome.language);
            //let kl = oWelcome.language.substring(oWelcome.language.indexOf('_'),2).toLowerCase()
            oKeyboard = await this.keyboard();
            oPartitions = await this.partitions(cryped, pve);
            oUsers = await this.users();
            oNetwork = await this.network();
        }
        await this.summary(oLocation, oKeyboard, oPartitions, oUsers);
        /**
         * INSTALL
         */
        const sequence = new krill_sequence_1.default(oLocation, oKeyboard, oPartitions, oUsers, oNetwork);
        await sequence.start(domain, this.unattended, this.nointeractive, this.halt, verbose);
    }
    /**
     * WELCOME
     */
    async welcome() {
        let language = this.krillConfig.language;
        if (language === '' || language === undefined) {
            language = await this.locales.getDefault(); // 'en_US.UTF-8'
        }
        let welcomeElem;
        while (true) {
            welcomeElem = react_1.default.createElement(welcome_1.default, { language: language });
            if (await confirm(welcomeElem, "Confirm Welcome datas?")) {
                break;
            }
            language = await (0, select_languages_1.default)(language);
        }
        return { language: language };
    }
    /**
     * LOCATION
     */
    async location(language) {
        let region = this.krillConfig.region;
        if (region === '' || region === undefined) {
            let region = shelljs_1.default.exec('cut -f1 -d/ < /etc/timezone', { silent: true }).stdout.trim();
        }
        let zone = this.krillConfig.zone;
        if (zone === '' || zone === undefined) {
            zone = shelljs_1.default.exec('cut -f2 -d/ < /etc/timezone', { silent: true }).stdout.trim();
        }
        // Try to auto-configure timezone by internet
        const url = `https://geoip.kde.org/v1/calamares`;
        try {
            const response = await axios_1.default.get(url);
            if (response.statusText === 'OK') {
                const data = JSON.stringify(response.data);
                const obj = JSON.parse(data);
                region = obj.time_zone.substring(0, obj.time_zone.indexOf('/'));
                zone = obj.time_zone.substring(obj.time_zone.indexOf('/') + 1);
            }
        }
        catch (error) {
            console.error('error: ' + error);
        }
        let locationElem;
        while (true) {
            locationElem = react_1.default.createElement(location_1.default, { language: language, region: region, zone: zone });
            if (await confirm(locationElem, "Confirm location datas?")) {
                break;
            }
            region = await (0, select_regions_1.default)(region);
            zone = await (0, select_zones_1.default)(region);
        }
        return {
            language: language,
            region: region,
            zone: zone
        };
    }
    /**
    * KEYBOARD
    */
    async keyboard() {
        let keyboardModel = this.krillConfig.keyboardModel;
        if (keyboardModel === '' || keyboardModel === undefined) {
            keyboardModel = await this.keyboards.getModel();
        }
        let keyboardLayout = this.krillConfig.keyboardLayout;
        if (keyboardLayout === '' || keyboardLayout === undefined) {
            keyboardLayout = await this.keyboards.getLayout();
        }
        let keyboardVariant = this.krillConfig.keyboardVariant;
        if (keyboardVariant === '' || keyboardVariant === undefined) {
            keyboardVariant = await this.keyboards.getVariant();
        }
        let keyboardOption = this.krillConfig.keyboardOption;
        if (keyboardOption === '' || keyboardOption === undefined) {
            keyboardOption = await this.keyboards.getOption();
        }
        let keyboardElem;
        while (true) {
            keyboardElem = react_1.default.createElement(keyboard_1.default, { keyboardModel: keyboardModel, keyboardLayout: keyboardLayout, keyboardVariant: keyboardVariant, keyboardOptions: keyboardOption });
            if (await confirm(keyboardElem, "Confirm Keyboard datas?")) {
                break;
            }
            else {
                keyboardModel = 'pc105';
                keyboardModel = await (0, select_keyboard_model_1.default)(keyboardModel);
                keyboardLayout = 'us';
                keyboardLayout = await (0, select_keyboard_layout_1.default)(keyboardLayout);
                keyboardVariant = '';
                keyboardVariant = await (0, select_keyboard_variant_1.default)(keyboardLayout);
                keyboardOption = '';
                keyboardOption = await (0, select_keyboard_option_1.default)(keyboardOption);
                if (keyboardModel === '') {
                    keyboardModel = 'pc105';
                }
            }
        }
        return {
            keyboardModel: keyboardModel,
            keyboardLayout: keyboardLayout,
            keyboardVariant: keyboardVariant,
            keyboardOption: keyboardOption
        };
    }
    /**
    * PARTITIONS
    */
    async partitions(crypted = false, pve = false) {
        // Calamares won't use any devices with iso9660 filesystem on it.
        const drives = shelljs_1.default.exec('lsblk |grep disk|cut -f 1 "-d "', { silent: true }).stdout.trim().split('\n');
        const driveList = [];
        drives.forEach((element) => {
            driveList.push('/dev/' + element);
        });
        let installationDevice = driveList[0]; // it was just /dev/sda before
        let installationMode = this.krillConfig.installationMode;
        if (installationMode === '' || installationMode === undefined) {
            installationMode = 'standard';
        }
        if (crypted) {
            installationMode = 'full-encrypted';
        }
        else if (pve) {
            installationMode = 'lvm2';
        }
        let filesystemType = 'ext4';
        let userSwapChoice = 'small';
        let partitionsElem;
        while (true) {
            partitionsElem = react_1.default.createElement(partitions_1.default, { installationDevice: installationDevice, installationMode: installationMode, filesystemType: filesystemType, userSwapChoice: userSwapChoice });
            if (await confirm(partitionsElem, "Confirm Partitions datas?")) {
                break;
            }
            else {
                installationDevice = '';
                installationMode = 'standard';
                if (crypted) {
                    installationMode = 'full-encrypted';
                }
                else if (pve) {
                    installationMode = 'lvm2';
                }
                filesystemType = '';
                userSwapChoice = '';
            }
            installationDevice = await (0, select_installation_device_1.default)();
            installationMode = await (0, select_installation_mode_1.default)();
            filesystemType = await (0, select_filesystem_type_1.default)();
            userSwapChoice = await (0, select_user_swap_choice_1.default)();
        }
        return {
            installationDevice: installationDevice,
            installationMode: installationMode,
            filesystemType: filesystemType,
            userSwapChoice: userSwapChoice
        };
    }
    /**
     * USERS
     */
    async users() {
        let name = this.krillConfig.name;
        if (name === '' || name === undefined) {
            name = 'artisan';
        }
        let fullname = this.krillConfig.fullname;
        if (fullname === '' || fullname === undefined) {
            fullname = name;
        }
        let password = this.krillConfig.password;
        if (password === '' || password === undefined) {
            password = 'evolution';
        }
        let rootPassword = this.krillConfig.rootPassword;
        if (rootPassword === '' || rootPassword === undefined) {
            rootPassword = 'evolution';
        }
        let hostname = this.krillConfig.hostname;
        if (hostname === '' || hostname === undefined) {
            hostname = shelljs_1.default.exec('cat /etc/hostname').trim();
        }
        let autologin = true;
        let sameUserPassword = true;
        let usersElem;
        while (true) {
            usersElem = react_1.default.createElement(users_1.default, { name: name, fullname: fullname, hostname: hostname, password: password, rootPassword: rootPassword, autologin: autologin, sameUserPassword: sameUserPassword });
            if (await confirm(usersElem, "Confirm Users datas?")) {
                break;
            }
            name = await (0, get_username_1.default)(name);
            fullname = await (0, get_userfullname_1.default)(fullname);
            password = await (0, get_password_1.default)(name, password);
            rootPassword = await (0, get_password_1.default)('root', password);
            hostname = await (0, get_hostname_1.default)(hostname);
        }
        return {
            name: name,
            fullname: fullname,
            password: password,
            rootPassword: rootPassword,
            autologin: autologin,
            hostname: hostname
        };
    }
    /**
     * Network
     */
    async network() {
        const i = {};
        const ifaces = fs_1.default.readdirSync('/sys/class/net/');
        i.iface = await utils_1.default.iface();
        i.addressType = 'dhcp';
        i.address = utils_1.default.address();
        i.netmask = utils_1.default.netmask();
        i.gateway = utils_1.default.gateway();
        i.dns = utils_1.default.getDns();
        i.domain = utils_1.default.getDomain();
        let dnsString = '';
        for (let c = 0; c < i.dns.length; c++) {
            dnsString += i.dns[c].trim();
            if (c < i.dns.length - 1) {
                dnsString += '; ';
            }
        }
        let networkElem;
        while (true) {
            networkElem = react_1.default.createElement(network_1.default, { iface: i.iface, addressType: i.addressType, address: i.address, netmask: i.netmask, gateway: i.gateway, domain: i.domain, dns: dnsString });
            if (await confirm(networkElem, "Confirm Network datas?")) {
                break;
            }
            i.iface = await (0, select_interface_1.default)(i.iface, ifaces);
            i.addressType = await (0, select_address_type_1.default)();
            if (i.addressType === 'static') {
                i.address = await (0, get_address_1.default)(i.address);
                i.netmask = await (0, get_netmask_1.default)(i.netmask);
                i.gateway = await (0, get_gateway_1.default)(i.gateway);
                i.domain = await (0, get_domain_1.default)(i.domain);
                if (i.domain.at(0) !== '.') {
                    i.domain = '.' + i.domain;
                }
                i.dns = (await (0, get_dns_1.default)(dnsString)).split(';');
                dnsString = '';
                for (let c = 0; c < i.dns.length; c++) {
                    dnsString += i.dns[c].trim();
                    if (c < i.dns.length - 1) {
                        dnsString += '; ';
                    }
                }
            }
        }
        return i;
    }
    /**
     * SUMMARY
     */
    async summary(location, keyboard, partitions, users) {
        let summaryElem;
        let message = "Double check the installation disk: " + partitions.installationDevice;
        if (this.unattended && this.nointeractive) {
            message = "Unattended installation will start in 5 seconds, press CTRL-C to abort!";
        }
        while (true) {
            summaryElem = react_1.default.createElement(summary_1.default, { name: users.name, password: users.password, rootPassword: users.rootPassword, hostname: users.hostname, region: location.region, zone: location.zone, language: location.language, keyboardModel: keyboard.keyboardModel, keyboardLayout: keyboard.keyboardLayout, installationDevice: partitions.installationDevice, message: message });
            if (this.unattended && this.nointeractive) {
                redraw(summaryElem);
                await sleep(5000);
                break;
            }
            else if (this.unattended && !this.nointeractive) {
                if (await confirm(summaryElem, "Read the Summary, confirm or abort")) {
                    break;
                }
                else {
                    process.exit(0);
                }
            }
            else if (await confirm(summaryElem, "Confirm Summary datas?")) {
                break;
            }
        }
    }
    /**
     * return true if pv exist
     */
    async pvExist() {
        let exist = false;
        const check = `#!/bin/sh\npvdisplay |grep "PV Name" >/dev/null && echo 1|| echo 0`;
        if (shelljs_1.default.exec(check).stdout.trim() === '1') {
            exist = true;
        }
        return exist;
    }
}
exports.default = Krill;
/**
 * confirm
 * @returns
 */
async function confirm(elem, msg = "Confirm") {
    redraw(elem);
    const result = JSON.parse(await utils_1.default.customConfirmAbort(msg));
    let retval = false;
    if (result.confirm === 'Yes') {
        retval = true;
    }
    else if (result.confirm === 'Abort') {
        process.exit();
    }
    return retval;
}
/**
 * Occorre farglierlo rigenerare a forza
 * anche quando NON cambiano i dati
 * forceUpdate
 */
function redraw(elem) {
    let opt = {};
    opt.patchConsole = true;
    opt.debug = false;
    console.clear();
    (0, ink_1.render)(elem, opt);
}
/**
 *
 * @param mask
 */
function netmask2CIDR(mask) {
    const countCharOccurences = (string, char) => string.split(char).length - 1;
    const decimalToBinary = (dec) => (dec >>> 0).toString(2);
    const getNetMaskParts = (nmask) => nmask.split('.').map(Number);
    const netmask2CIDR = (netmask) => countCharOccurences(getNetMaskParts(netmask)
        .map(part => decimalToBinary(part))
        .join(''), '1');
}
/**
 *
 * @param ms
 * @returns
 */
function sleep(ms = 0) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}
