"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to set private field on non-instance");
    }
    privateMap.set(receiver, value);
    return value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to get private field on non-instance");
    }
    return privateMap.get(receiver);
};
var _connect;
var ShellService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ShellService = exports.SshExtended = void 0;
const common_1 = require("@nestjs/common");
const http = require("http");
const lodash_1 = require("lodash");
const node_ssh_1 = require("node-ssh");
const ssh2 = require("ssh2");
const config_service_1 = require("../config/config.service");
const ws_gateway_1 = require("../ws/ws.gateway");
class SshExtended extends node_ssh_1.NodeSSH {
    constructor(config) {
        super();
        this.config = config;
        _connect.set(this, void 0);
        __classPrivateFieldSet(this, _connect, node_ssh_1.NodeSSH.prototype.connect);
    }
    connect() {
        return __classPrivateFieldGet(this, _connect).call(this, this.config);
    }
}
exports.SshExtended = SshExtended;
_connect = new WeakMap();
let ShellService = ShellService_1 = class ShellService {
    constructor(configService, wsGateway) {
        this.configService = configService;
        this.wsGateway = wsGateway;
    }
    async onModuleInit() {
        Promise.all(this.configService.config.nodes.map((conn) => this.setupClient(conn)));
    }
    getNodeHttpAgent(conn) {
        const agent = new ssh2.HTTPAgent(conn);
        agent.defaultPort = 3000;
        return agent;
    }
    async setupClient(conn) {
        let apiIsInstalled = false;
        apiIsInstalled = await new Promise((resolve) => {
            try {
                http.get({
                    path: '/api/v1/sysinfo/localhost/general',
                    agent: this.getNodeHttpAgent(conn),
                    headers: { Connection: 'close' }
                }, (res) => {
                    if ((res === null || res === void 0 ? void 0 : res.statusCode) === 200)
                        return resolve(true);
                    resolve(false);
                }).on('error', (error) => {
                    common_1.Logger.warn(error.message, ShellService_1.name);
                    resolve(false);
                });
            }
            catch (error) {
                common_1.Logger.error(error.message, error.stack, ShellService_1.name);
                resolve(false);
            }
        });
        if (apiIsInstalled)
            return;
        try {
            const client = await this.createAndConnectClient(conn);
            await this.installNodeLTS(client);
            await this.installFreshClientAPI(client);
            client.connection.end();
        }
        catch (error) {
            common_1.Logger.error(error.message, error.stack, ShellService_1.name);
        }
    }
    async getShellByHostname(host) {
        const node = this.configService.config.nodes.find(p => p.host === host);
        if (!node)
            throw new common_1.BadRequestException('Not node found');
        return this.createAndConnectClient(node);
    }
    async createAndConnectClient({ username, password, host, port, index }) {
        const client = new SshExtended({
            host,
            port,
            username,
            password,
        });
        await client.connect();
        common_1.Logger.verbose(`Connected to ssh server: ${host}`);
        return client;
    }
    async dockerIsInstalled(client) {
        var _a;
        const result = await this.runCommand('docker -v', client);
        return { isInstalled: ((_a = result.outputs[0]) === null || _a === void 0 ? void 0 : _a.startsWith('v')) || false, version: result.outputs[0] };
    }
    async nodeIsInstalled(client) {
        var _a;
        const result = await this.runCommand('node -v', client);
        return { isInstalled: ((_a = result.outputs[0]) === null || _a === void 0 ? void 0 : _a.startsWith('v')) || false, version: result.outputs[0] };
    }
    async npmIsInstalled(client) {
        var _a, _b;
        const result = await this.runCommand('npm -v', client);
        return { isInstalled: ((_b = (_a = result.outputs[0]) === null || _a === void 0 ? void 0 : _a.split('.')) === null || _b === void 0 ? void 0 : _b.length) === 3 || false, version: result.outputs[0] };
    }
    async nIsInstalled(client) {
        var _a, _b;
        const result = await this.runCommand('n -V', client);
        return { isInstalled: ((_b = (_a = result.outputs[0]) === null || _a === void 0 ? void 0 : _a.split('.')) === null || _b === void 0 ? void 0 : _b.length) === 3 || false, version: result.outputs[0] };
    }
    async updateApt(client) {
        const result = await this.runCommand('sudo apt update', client);
        if (result.code)
            throw new Error(JSON.stringify(result, null, 2));
        return result;
    }
    async installFreshClientAPI(client) {
        await this.installNodeLTS(client);
        await this.runCommand('npm i --no-audit --no-progress -g pm2 && pm2 startup', client);
        await this.runCommand('pm2 delete shellops-api', client);
        await this.runCommand('rimraf ~/shellops-api', client);
        await this.runCommand('rimraf ~/.shellops.json', client);
        await this.runCommand('git clone https://github.com/shellops/shellops-api.git ~/shellops-api', client);
        await this.runCommand('cd ~/shellops-api && npm i --no-audit --prefer-offline --no-progress', client);
        await this.runCommand('cd ~/shellops-api && npm run build', client);
        await this.runCommand('cd ~/shellops-api && pm2 start dist/main.js --name shellops-api', client);
        await this.runCommand('pm2 save', client);
    }
    async installNode(client) {
        if ((await this.nodeIsInstalled(client)).isInstalled)
            return;
        const result = await this.runCommand('sudo apt install nodejs -y', client);
        if (result.code)
            throw new Error(JSON.stringify(result, null, 2));
    }
    async installNpm(client) {
        if ((await this.npmIsInstalled(client)).isInstalled)
            return;
        const result = await this.runCommand('sudo apt install npm -y', client);
        if (result.code)
            throw new Error(JSON.stringify(result, null, 2));
    }
    async installDnsProxy(client) {
        const result = await this.runCommand(['sudo apt install resolvconf',
            'sudo systemctl status resolvconf.service',
            'sudo systemctl start resolvconf.service',
            'sudo systemctl enable resolvconf.service',
            'sudo systemctl status resolvconf.service',
            'sudo rm /etc/resolvconf/resolv.conf.d/head',
            'sudo echo "nameserver 178.22.122.100" >> /etc/resolvconf/resolv.conf.d/head',
            'sudo echo "nameserver 185.51.200.2" >> /etc/resolvconf/resolv.conf.d/head',
            'sudo systemctl stop resolvconf.service',
            'sudo systemctl start resolvconf.service',].join(' ; '), client);
        if (result.code)
            throw new Error(JSON.stringify(result, null, 2));
    }
    async installDockerAptKey(client) {
        const aptKeyInstallResult = await this.runCommand('curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -', client);
        if (aptKeyInstallResult.code)
            if (aptKeyInstallResult.errors.find(p => p.includes('403'))) {
                await this.installDnsProxy(client);
                await this.installDockerAptKey(client);
                return;
            }
            else
                throw new Error(JSON.stringify(aptKeyInstallResult, null, 2));
    }
    async uninstallDocker(client) {
        await this.runCommand('sudo apt remove docker-ce containerd.io -y', client);
        await this.runCommand('sudo apt remove docker-ce-cli -y', client);
    }
    async installDocker(client) {
        const depInstallResult = await this.runCommand('sudo apt-get install -y ' +
            ['apt-transport-https',
                'ca-certificates',
                'curl',
                'gnupg-agent',
                'software-properties-common'].join(' '), client);
        if (depInstallResult.code)
            throw new Error(JSON.stringify(depInstallResult, null, 2));
        await this.installDockerAptKey(client);
        const addRepoResult = await this.runCommand([
            'sudo add-apt-repository ',
            '"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"',
        ].join(' '), client);
        if (addRepoResult.code)
            throw new Error(JSON.stringify(addRepoResult, null, 2));
        await this.updateApt(client);
        const installDockerResult = await this.runCommand('sudo apt-get install -y docker-ce docker-ce-cli containerd.io', client);
        if (installDockerResult.code)
            throw new Error(JSON.stringify(installDockerResult, null, 2));
        return installDockerResult;
    }
    async installN(client) {
        await this.installNode(client);
        await this.installNpm(client);
        if ((await this.nIsInstalled(client)).isInstalled)
            return;
        const installResult = await this.runCommand('sudo npm i -g n', client);
        if (installResult.code)
            throw new Error(JSON.stringify(installResult, null, 2));
        await this.runCommand('PATH="$PATH"', client);
    }
    async latestNodeLtsAvailable(client) {
        const result = await this.runCommand('n --lts', client);
        if (result.code)
            throw new Error(JSON.stringify(result, null, 2));
        return result;
    }
    async installNodeLTS(client) {
        await this.installN(client);
        const { outputs: [latestNodeVersion] } = await this.latestNodeLtsAvailable(client);
        const { version: currentNodeVersion } = await this.nodeIsInstalled(client);
        if (latestNodeVersion === currentNodeVersion)
            return;
        const result = await this.runCommand('n lts', client);
        if (result.code)
            throw new Error(JSON.stringify(result, null, 2));
    }
    async runCommand(command, client, opts) {
        var _a, _b;
        common_1.Logger.verbose(`EXEC(${client.config.host}) -> '${command}' on `);
        const result = await client.execCommand(command, {
            cwd: '/home',
            execOptions: { pty: true },
            onStdout: (_chunk) => {
                const chunk = lodash_1.trim(_chunk.toString(), ' \r\n\t');
                if (opts === null || opts === void 0 ? void 0 : opts.onStdout)
                    opts.onStdout(chunk);
                this.wsGateway.broadcast(JSON.stringify({
                    type: 'ShellCommandResultDto.output',
                    data: {
                        command,
                        output: chunk,
                        host: client.config.host,
                    }
                }));
                common_1.Logger.verbose(`STDOUT(${client.config.host}) -> ${chunk}`);
            },
            onStderr: (_chunk) => {
                const chunk = lodash_1.trim(_chunk.toString(), ' \r\n\t');
                if (opts === null || opts === void 0 ? void 0 : opts.onStderr)
                    opts.onStderr(chunk);
                this.wsGateway.broadcast(JSON.stringify({
                    type: 'ShellCommandResultDto.error',
                    data: {
                        command,
                        error: chunk,
                        host: client.config.host,
                    }
                }));
                common_1.Logger.verbose(`STDERR(${client.config.host}) -> ${chunk}`);
            }
        });
        const model = {
            host: client.config.host,
            code: result.code,
            signal: result.signal,
            errors: (_a = result.stderr) === null || _a === void 0 ? void 0 : _a.split('\n'),
            outputs: (_b = result.stdout) === null || _b === void 0 ? void 0 : _b.split('\n')
        };
        return model;
    }
};
ShellService = ShellService_1 = __decorate([
    common_1.Injectable(),
    __metadata("design:paramtypes", [config_service_1.ConfigService,
        ws_gateway_1.WsGateway])
], ShellService);
exports.ShellService = ShellService;
//# sourceMappingURL=shell.service.js.map