#!/usr/bin/node
// Simple systemd unit generator for etcd
// Copyright (c) Vitaliy Filippov, 2019+
// License: MIT

// USAGE:
// 1) Put the same etcd_address into /etc/vitastor/vitastor.conf on all monitor nodes
// 2) Run ./make-etcd.js. It will create the etcd service on one of specified IPs

const child_process = require('child_process');
const fs = require('fs');
const os = require('os');

run().catch(e => { console.error(e); process.exit(1); });

async function run()
{
    const config_path = process.argv[2] || '/etc/vitastor/vitastor.conf';
    if (config_path == '-h' || config_path == '--help')
    {
        console.log(
            'Initialize systemd etcd service for Vitastor\n'+
            '(c) Vitaliy Filippov, 2019+ (MIT)\n'+
            '\n'+
            'USAGE:\n'+
            '1) Put the same etcd_address into /etc/vitastor/vitastor.conf on all monitor nodes\n'+
            '2) Run '+process.argv[1]+' [config_path]\n'
        );
        process.exit(0);
    }
    if (!fs.existsSync(config_path))
    {
        console.log(config_path+' is missing');
        process.exit(1);
    }
    const in_docker = fs.existsSync("/etc/vitastor/etcd.conf") &&
        fs.existsSync("/etc/vitastor/docker.conf");
    if (!in_docker && fs.existsSync("/etc/systemd/system/vitastor-etcd.service"))
    {
        console.log("/etc/systemd/system/vitastor-etcd.service already exists");
        process.exit(1);
    }
    if (!in_docker && fs.existsSync("/etc/systemd/system/etcd.service"))
    {
        console.log("/etc/systemd/system/etcd.service already exists");
        process.exit(1);
    }
    const config = JSON.parse(fs.readFileSync(config_path, { encoding: 'utf-8' }));
    if (!config.etcd_address)
    {
        console.log("etcd_address is missing in "+config_path);
        process.exit(1);
    }
    const etcds = (config.etcd_address instanceof Array ? config.etcd_address : (''+config.etcd_address).split(/,/))
        .map(s => (''+s).replace(/^https?:\/\/|(:\d+)?(\/.*)?$/g, '').replace(/^\[(.*)\]$/, '$1').toLowerCase());
    const num = select_local_etcd(etcds);
    if (num < 0)
    {
        console.log('No matching IPs in etcd_address from '+config_path);
        process.exit(0);
    }
    const etcd_url = 'http://' + (etcds[num].indexOf(':') >= 0 ? '['+etcds[num]+']' : etcds[num]);
    const etcd_name = 'etcd'+etcds[num].replace(/[^0-9a-z_]/ig, '_');
    const etcd_cluster = etcds.map(e => `etcd${e.replace(/[^0-9a-z_]/ig, '_')}=http://${e.indexOf(':') >= 0 ? '['+e+']' : e}:2380`).join(',');
    if (in_docker)
    {
        let etcd_conf = fs.readFileSync("/etc/vitastor/etcd.conf", { encoding: 'utf-8' });
        etcd_conf = replace_env(etcd_conf, 'ETCD_NAME', etcd_name);
        etcd_conf = replace_env(etcd_conf, 'ETCD_IP', etcds[num]);
        etcd_conf = replace_env(etcd_conf, 'ETCD_INITIAL_CLUSTER', etcd_cluster);
        fs.writeFileSync("/etc/vitastor/etcd.conf", etcd_conf);
        console.log('etcd for Vitastor configured. Run `systemctl enable --now vitastor-etcd` to start etcd');
        process.exit(0);
    }
    await system(`mkdir -p /var/lib/etcd/vitastor`);
    fs.writeFileSync(
        "/etc/systemd/system/vitastor-etcd.service",
`[Unit]
Description=etcd for vitastor
After=network-online.target local-fs.target time-sync.target
Wants=network-online.target local-fs.target time-sync.target

[Service]
Restart=always
Environment=GOGC=50
ExecStart=etcd --name ${etcd_name} --data-dir /var/lib/etcd/vitastor \\
    --snapshot-count 10000 --advertise-client-urls ${etcd_url}:2379 --listen-client-urls ${etcd_url}:2379 \\
    --initial-advertise-peer-urls ${etcd_url}:2380 --listen-peer-urls ${etcd_url}:2380 \\
    --initial-cluster-token vitastor-etcd-1 --initial-cluster ${etcd_cluster} \\
    --initial-cluster-state new --max-txn-ops=100000 --max-request-bytes=104857600 \\
    --auto-compaction-retention=10 --auto-compaction-mode=revision
WorkingDirectory=/var/lib/etcd/vitastor
ExecStartPre=+chown -R etcd /var/lib/etcd/vitastor
User=etcd
PrivateTmp=false
TasksMax=infinity
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target
`);
    await system(`useradd etcd`);
    await system(`systemctl daemon-reload`);
    // Disable distribution etcd unit and enable our one
    await system(`systemctl disable --now etcd`);
    await system(`systemctl enable --now vitastor-etcd`);
    process.exit(0);
}

function replace_env(text, key, value)
{
    let found = false;
    text = text.replace(new RegExp('^'+key+'\\s*=.*', 'm'), () => { found = true; return key+'='+value; });
    return found ? text : text.replace(/\s*$/, '\n')+key+'='+value+'\n';
}

function select_local_etcd(etcds)
{
    const ifaces = os.networkInterfaces();
    for (const ifname in ifaces)
        for (const iface of ifaces[ifname])
            for (let i = 0; i < etcds.length; i++)
                if (etcds[i] == iface.address.toLowerCase())
                    return i;
    return -1;
}

async function system(cmd)
{
    const cp = child_process.spawn(cmd, { shell: true, stdio: [ 0, 1, 2 ] });
    let finish_cb;
    cp.on('exit', () => finish_cb && finish_cb());
    if (cp.exitCode == null)
        await new Promise(ok => finish_cb = ok);
    return cp.exitCode;
}
