"use strict";
/**
 * penguins-eggs
 * command: syncto.ts
 * author: Piero Proietti
 * email: piero.proietti@gmail.com
 * license: MIT
 */
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
/**
 *
 * syncfrom (restore)
 * --include-from file.list // if only include is provided everything from the list if used to update the system.
 * --exclude-from file-list // it just updates the system
 * --delete
 *
* If both options are provided then it works as a combination as provided in the link above.
* https://stackoverflow.com/questions/19296190/rsync-include-from-vs-exclude-from-what-is-the-actual-difference
*
 * The same logic is applied for the syncto also.
 *
 * On top of all of this the --delete option
 * if needed to be passed so that everything else is removed, but this
 * this should not be available by default
 */
const core_1 = require("@oclif/core");
const fs_1 = tslib_1.__importDefault(require("fs"));
const path_1 = tslib_1.__importDefault(require("path"));
const utils_1 = tslib_1.__importDefault(require("../classes/utils"));
const utils_2 = require("../lib/utils");
// backup
const promises_1 = require("fs/promises");
const fs_2 = require("fs");
const users_1 = tslib_1.__importDefault(require("../classes/users"));
/**
 *
 */
class Syncto extends core_1.Command {
    constructor() {
        super(...arguments);
        this.verbose = false;
        this.echo = {};
        this.luksName = 'luks-eggs-backup';
        this.luksFile = `/run/live/medium/live/${this.luksName}`;
        this.luksDevice = `/dev/mapper/${this.luksName}`;
        this.luksMountpoint = '/tmp/eggs-backup';
    }
    /**
     *
     */
    async run() {
        const { flags } = await this.parse(Syncto);
        if (flags.verbose) {
            this.verbose = true;
        }
        this.echo = utils_1.default.setEcho(this.verbose);
        let fileVolume = '';
        if (flags.file) {
            fileVolume = flags.file;
        }
        let destDelete = false;
        if (flags.delete) {
            destDelete = true;
        }
        if (utils_1.default.isRoot()) {
            /**
                 * restore con file
                 */
            if (fileVolume === '') {
                fileVolume = '/tmp/luks-eggs-data';
            }
            this.luksName = path_1.default.basename(fileVolume);
            this.luksFile = fileVolume;
            this.luksDevice = `/dev/mapper/${this.luksName}`;
            this.luksMountpoint = '/tmp/eggs-data';
            if (!fs_1.default.existsSync(fileVolume)) {
                await this.luksCreate();
            }
            else {
                utils_1.default.warning(`LUKS volume: ${this.luksFile} exist, don't need create`);
            }
            if (fs_1.default.existsSync(fileVolume)) {
                await this.luksOpen();
                await this.backup(destDelete);
                await this.luksClose();
            }
        }
        else {
            utils_1.default.useRoot(this.id);
        }
    }
    /**
     *
     */
    async backup(destDelete = false) {
        if (this.verbose) {
            utils_1.default.warning('backup');
        }
        utils_1.default.warning(`Saving users' data on ${this.luksFile}`);
        const usersArray = await this.usersFill();
        for (const element of usersArray) {
            if (element.saveIt && fs_1.default.existsSync(element.home)) {
                await (0, utils_2.exec)(`mkdir -p ${this.luksMountpoint}/ROOT${element.home}`, this.echo);
                const source = element.home;
                let dest = this.luksMountpoint + '/ROOT' + element.home;
                dest = dest.slice(0, Math.max(0, dest.lastIndexOf('/')));
                let cmd = `rsync --archive ${source} ${dest}`;
                if (destDelete) {
                    cmd = `rsync --archive --delete ${source} ${dest}`;
                }
                await (0, utils_2.exec)(cmd, this.echo);
            }
        }
        utils_1.default.warning(`Saving users' accounts on ${this.luksFile}`);
        await (0, utils_2.exec)(`mkdir -p ${this.luksMountpoint}/etc`, this.echo);
        await (0, utils_2.exec)(`cp /etc/passwd ${this.luksMountpoint}/etc`, this.echo);
        await (0, utils_2.exec)(`cp /etc/shadow ${this.luksMountpoint}/etc`, this.echo);
        await (0, utils_2.exec)(`cp /etc/group ${this.luksMountpoint}/etc`, this.echo);
    }
    /**
    * usersFill
    */
    async usersFill() {
        const usersArray = [];
        await (0, promises_1.access)('/etc/passwd', fs_2.constants.R_OK | fs_2.constants.W_OK);
        const passwd = fs_1.default.readFileSync('/etc/passwd', 'utf-8').split('\n');
        for (const element of passwd) {
            const line = element.split(':');
            const users = new users_1.default(line[0], line[1], line[2], line[3], line[4], line[5], line[6]);
            await users.getValues();
            if (users.password !== undefined) {
                usersArray.push(users);
            }
        }
        return usersArray;
    }
    /**
     *
     */
    async luksCreate() {
        utils_1.default.warning(`Creating LUKS Volume on ${this.luksFile}`);
        let totalSize = 0;
        console.log('I will extimate volume size from your private data:');
        const users = await this.usersFill();
        for (const user of users) {
            if (user.login !== 'root' && user.saveIt) {
                let utype = 'user   ';
                if (Number.parseInt(user.uid) < 1000) {
                    utype = 'service';
                }
                console.log(`- ${utype}: ${user.login.padEnd(16)} \thome: ${user.home} \tsize: ${utils_1.default.formatBytes(user.size)} \tBytes: ${user.size} `);
                totalSize += user.size;
            }
        }
        console.log(`Total\t\t\t\t\t\t\tsize: ${utils_1.default.formatBytes(totalSize)} \tBytes: ${totalSize}`);
        /**
           * after we get size, we can start building luks-volume
           */
        const binaryHeaderSize = 4194304;
        const volumeSize = totalSize * 1.5 + binaryHeaderSize;
        // Deciding blockSize and blocks
        const blockSize = 512;
        let blocks = Math.ceil(volumeSize / blockSize);
        // We need a minimum size of 32 MB
        const minimunSize = 134217728; // 128 * 1024 *1024
        if (totalSize < minimunSize) { }
        if (blocks * blockSize < minimunSize) {
            blocks = Math.ceil(minimunSize / blockSize);
        }
        utils_1.default.warning(`Creating an encrypted file ${this.luksFile} blocks=${blocks}, block size: ${blockSize}, size: ${utils_1.default.formatBytes(blocks * blockSize)}`);
        await (0, utils_2.exec)(`dd if=/dev/zero of=${this.luksFile} bs=${blockSize} count=${blocks}`, this.echo);
        // find first unused device
        const findFirstUnusedDevice = await (0, utils_2.exec)('losetup -f', { echo: this.verbose, ignore: false, capture: true });
        let firstUnusedDevice = '';
        if (findFirstUnusedDevice.code !== 0) {
            utils_1.default.pressKeyToExit(`Error: ${findFirstUnusedDevice.code} ${findFirstUnusedDevice.data}`);
        }
        else {
            firstUnusedDevice = findFirstUnusedDevice.data.trim();
        }
        await (0, utils_2.exec)(`losetup ${firstUnusedDevice} ${this.luksFile}`, this.echo);
        // Utils.warning('Enter a large string of random text below to setup the pre-encryption')
        await (0, utils_2.exec)(`cryptsetup -y -v --type luks2 luksFormat  ${this.luksFile}`, utils_1.default.setEcho(true));
        // Utils.warning(`Enter the desired passphrase for the encrypted ${this.luksName} below`)
        const crytoSetup = await (0, utils_2.exec)(`cryptsetup luksOpen --type luks2 ${this.luksFile} ${this.luksName}`, utils_1.default.setEcho(true));
        if (crytoSetup.code !== 0) {
            utils_1.default.pressKeyToExit(`Error: ${crytoSetup.code} ${crytoSetup.data}`);
        }
        utils_1.default.warning(`Formatting ${this.luksDevice} to ext2`);
        const formatting = await (0, utils_2.exec)(`sudo mkfs.ext2 ${this.luksDevice}`, this.echo);
        if (formatting.code !== 0) {
            utils_1.default.pressKeyToExit(`Error: ${formatting.code} ${formatting.data}`);
        }
        // this.luksClose()
    }
    /**
     *
     */
    async luksOpen() {
        if (!fs_1.default.existsSync(this.luksDevice)) {
            // Utils.warning(`LUKS open volume: ${this.luksName}`)
            await (0, utils_2.exec)(`cryptsetup luksOpen --type luks2 ${this.luksFile} ${this.luksName}`, utils_1.default.setEcho(true));
        }
        else {
            utils_1.default.warning(`LUKS volume: ${this.luksName} already open`);
        }
        if (!fs_1.default.existsSync(this.luksMountpoint)) {
            await (0, utils_2.exec)(`mkdir -p ${this.luksMountpoint}`, this.echo);
        }
        if (!utils_1.default.isMountpoint(this.luksMountpoint)) {
            utils_1.default.warning(`mount volume: ${this.luksDevice} on ${this.luksMountpoint}`);
            await (0, utils_2.exec)(`mount ${this.luksDevice} ${this.luksMountpoint}`, this.echo);
        }
        else {
            utils_1.default.warning(`mount volume: ${this.luksDevice} already mounted on ${this.luksMountpoint}`);
        }
    }
    /**
    *
    */
    async luksClose() {
        if (utils_1.default.isMountpoint(this.luksMountpoint)) {
            await (0, utils_2.exec)(`umount ${this.luksMountpoint}`, this.echo);
        }
        if (fs_1.default.existsSync(this.luksDevice)) {
            utils_1.default.warning(`LUKS close volume: ${this.luksName}`);
            await (0, utils_2.exec)(`cryptsetup luksClose ${this.luksName}`, this.echo);
        }
    }
}
Syncto.flags = {
    delete: core_1.Flags.string({ description: 'rsync --delete delete extraneous files from dest dirs' }),
    file: core_1.Flags.string({ char: 'f', description: 'file LUKS volume encrypted' }),
    help: core_1.Flags.help({ char: 'h' }),
    verbose: core_1.Flags.boolean({ char: 'v', description: 'verbose' }),
};
Syncto.description = 'saves users and user data in a LUKS volume inside the iso';
Syncto.examples = [
    'sudo eggs syncto',
    'sudo eggs syncto --file /path/to/fileLUKS',
];
exports.default = Syncto;
