const { argv } = require("process"); const fs = require("fs-extra"); const mustache = require("mustache"); const bodyParser = require("body-parser"); const glob = require("glob"); const path = require("path"); const cors = require("cors"); const express = require("express"); const process = require("process"); /******************************************* SEE https://gitea.ndda.fr/apxtrib/apxtrib/wiki/Devrules To have a quick understanding and convention before doing deeply in source code To share configuration : process.env.dirtown is folder where town folder name /townId-nationId is accessible const conf = require(`${process.env.dirtown}/conf.json`); app.locals.tribeids is defined later in apixtrib.js and allow express app to always have in memory a dynamic of tribeId available in req.app.locals.tribeids */ /** * 1st install for dev * run $ node apxtrib.js nationId:ants townId:devfarm dns:devfarm-ants * then just yarn dev * it create a folder outside ../townId-nationId/ * To convert a dev into a chain town run again with relevant param: * run $ node apxtrib.js nationId:ants townId:devfarm dns:devfarm-ants * check the web interface http://dns * then just yarn startpm2 your town is under PM2 control * * * @param {args} args key:value example node apxtrib nationId:ants townId:devfarm dns:devfarm-ants * if no parammeter from adminapi/www/adminapx/conf/setup_xx.json * * Keyword townId = "devfarm" then this is unchain town to dev * else this is a production town ready to chain to the nationId * * @returns listen onto http:/dns (80 and 443) for admin webapp and http://localhost:initconf.api.port * by setting the nginx parameter * A folder for town data is created at the same level than apxtrib as /townId-nationId/conf.json ... */ const setconf = (param) => { // set conf from argv = param={nationId,townId,dns} console.log( `RUNNING A NEW SETUP with nation ${param.nationId} and town ${param.townId} to be accessible in dns http://${param.dns}` ); fs.outputJsonSync( `${__dirname}/adminapi/www/adminapx/conf/setup_xx.json`, { nationId: param.nationId, townId: param.townId, dns: [param.dns], comment: "Auto generate setup from apxtrib after node apxtrib nationId:value townId:value dns:domaine_to_access", }, { space: 2 } ); // Add this town localy const townid = { townId: param.townId, nationId: param.nationId, dns: param.dns, IP: "127.0.0.1", status: "unchain", tribes: [], }; const townidkey = {}; townidkey[param.townId] = townid; fs.outputJsonSync(`./nationchains/towns/idx/townId_all.json`, townidkey); fs.outputJsonSync(`./nationchains/towns/itm/${param.townId}.json`, townid); initconf = fs.readJsonSync("./adminapi/www/adminapx/conf/initconf.json"); initconf.dirapi = __dirname; initconf.dirtown = path.resolve( `${__dirname}/../${param.townId}-${param.nationId}` ); initconf.nationId = param.nationId; initconf.townId = param.townId; initconf.sudoerUser = process.env.USER; if (!initconf.dns.includes(param.dns)) { initconf.dns.push(param.dns); } initconf.nginx.include.push(`${initconf.dirapi}/adminapi/www/nginx_*.conf`); initconf.nginx.include.push( path.resolve( `../${param.townId}-${param.nationId}/tribes/**/www/nginx_*.conf` ) ); initconf.nginx.logs = `${initconf.dirtown}/logs/nginx/adminapx`; initconf.nginx.website = "adminapx"; initconf.nginx.fswww = `${__dirname}/adminapi/www`; initconf.nginx.pageindex = "index_en.html"; const { exec } = require("child_process"); exec( `sudo chown -R ${process.env.USER}:${process.env.USER} /etc/nginx`, (error, stdout, stderr) => { if (error) { console.log("\x1b[42m", error, stdout, stderr, "x1b[0m"); console.log("impossible to change owner of /etc/nginx by phil:phil"); process.exit(); } else { console.log( `successfull sudo chown -R ${process.env.USER}:${process.env.USER} /etc/nginx` ); } } ); fs.outputJsonSync( `../${param.townId}-${param.nationId}/conf.json`, initconf, { space: 2 } ); fs.ensureDirSync(`../${param.townId}-${param.nationId}/logs/nginx`); fs.ensureDirSync(`../${param.townId}-${param.nationId}/tmp/tokens`); const nginxconf = fs.readFileSync( "./adminapi/www/adminapx/conf/nginx.conf.mustache", "utf8" ); const proxyparams = fs.readFileSync( "./adminapi/www/adminapx/conf/nginxproxyparams.mustache", "utf8" ); const websiteconf = fs.readFileSync( "./adminapi/www/adminapx/conf/nginxmodelwebsite.conf.mustache", "utf8" ); // saved and change nginx conf if (!fs.existsSync("/etc/nginx/nginxconf.saved")) { fs.moveSync("/etc/nginx/nginx.conf", "/etc/nginx/nginxconf.saved"); console.log( "your previous /etc/nginx/nginx.conf was backup in /etc/nginx/nginxconf.saved" ); } fs.outputFileSync( "/etc/nginx/nginx.conf", mustache.render(nginxconf, initconf), "utf8" ); fs.outputFileSync( "/etc/nginx/proxy_params", mustache.render(proxyparams, initconf), "utf8" ); fs.outputFileSync( `${__dirname}/adminapi/www/nginx_adminapx.conf`, mustache.render(websiteconf, initconf), "utf8" ); exec(initconf.nginx.restart, (error, stdout, stderr) => { if (error) { console.log("\x1b[42m", error, stdout, stderr, "x1b[0m"); //@todo supprimer la derniere config nginx et relancer fs.moveSync("/etc/nginx/nginxconf.saved", "/etc/nginx/nginx.conf"); console.log("Restart yarn dev"); } else { console.log(`ready to use http://${param.dns}`); } }); }; // check nginx exist if (!fs.existsSync("/etc/nginx/nginx.conf")) { console.log( "\x1b[31m Check documentation, nginx have to be installed on this server first, no /etc/nginx/nginx.conf available, install then rerun yarn command." ); process.exit(); } const param = {}; argv.slice(2).forEach((arg) => { const kv = arg.split(":"); if (kv.length == 2) { param[kv[0]] = kv[1]; } }); if ( Object.keys(param).length > 0 && param.nationId && param.townId && param.dns ) { setconf(param); } // setup_xx.json is gitignore so at first install we are in dev configuration let infotown = { nationId: "ants", townId: "devfarm", dns: ["devfarm-ants"], comment: "Auto generate setup from apxtrib after node apxtrib nationId:value townId:value dns:domaine_to_access", }; if (fs.existsSync(`${__dirname}/adminapi/www/adminapx/conf/setup_xx.json`)) { infotown = fs.readJsonSync( `${__dirname}/adminapi/www/adminapx/conf/setup_xx.json` ); } else { fs.outputJsonSync( `${__dirname}/adminapi/www/adminapx/conf/setup_xx.json`, infotown ); } if ( !fs.existsSync( path.resolve( `${__dirname}/../${infotown.townId}-${infotown.nationId}/conf.json` ) ) || !fs.existsSync(`${__dirname}/adminapi/www/nginx_adminapx.conf`) ) { // Case of new town or request a reset of dns to access adminapx setconf(infotown); } const conf = require(path.resolve( `${__dirname}/../${infotown.townId}-${infotown.nationId}/conf.json` )); process.env.dirtown = conf.dirtown; // Create and update ./nationchains const { updateobjectsfromfreshesttown } = require("./api/models/Nations.js"); updateobjectsfromfreshesttown(conf.towns, { pagans: "alias_all.json", towns: "townId_all.json", nations: "nationId_all.json", }); // Run main express process for a /towId-nationId/tribes let tribelist = {}; if (fs.existsSync(`${conf.dirtown}/tribes/idx/tribeId_all.json`)) { tribelist = fs.readJsonSync(`${conf.dirtown}/tribes/idx/tribeId_all.json`); } let doms = conf.dns; // only dns of town during the init process let tribeIds = []; let routes = glob.sync(`${conf.dirapi}/api/routes/*.js`).map((f) => { return { url: `/${path.basename(f, ".js")}`, route: f }; }); //routes={url,route} check how to add plugin tribe route later // keep only the 2 last part (.) of domain name to validate cors with it (generic domain) Object.keys(tribelist).forEach((t) => { tribelist[t].dns.forEach((d) => { const dm = d.split(".").slice(-2).join("."); if (!doms.includes(dm)) doms.push(dm); }); tribeIds.push(t); }); console.log("Allowed DOMs to access to this apxtrib server: ", doms); const app = express(); // load express parameter from conf Object.keys(conf.api.appset).forEach((p) => { app.set(p, conf.api.appset[p]); }); // To set depending of data form or get size to send app.use(bodyParser.urlencoded(conf.api.bodyparse.urlencoded)); // To set depending of post put json data size to send app.use(express.json()); app.use(bodyParser.json(conf.api.bodyparse.json)); app.locals.tribeids = tribeIds; console.log("app.locals.tribeids", app.locals.tribeids); // Cors management const corsOptions = { origin: (origin, callback) => { if (origin === undefined) { callback(null, true); } else if (origin.indexOf("chrome-extension") > -1) { callback(null, true); } else { //console.log( 'origin', origin ) //marchais avant modif eslint const rematch = ( /^https?\:\/\/(.*)\:.*/g ).exec( origin ) const rematch = /^https?:\/\/(.*):.*/g.exec(origin); //console.log( rematch ) let tmp = origin.replace(/http.?:\/\//g, "").split("."); if (rematch && rematch.length > 1) tmp = rematch[1].split("."); //console.log( 'tmp', tmp ) let dom = tmp[tmp.length - 1]; if (tmp.length > 1) { dom = `${tmp[tmp.length - 2]}.${tmp[tmp.length - 1]}`; } console.log( `origin: ${origin}, dom:${dom}, CORS allowed? : ${doms.includes(dom)}` ); if (doms.includes(dom)) { callback(null, true); } else { console.log(`Origin is not allowed by CORS`); callback(new Error("Not allowed by CORS")); } } }, exposedHeaders: Object.keys(conf.api.exposedHeaders), }; // CORS app.use(cors(corsOptions)); // Static Routes // try to use nginx route instead in comments /*app.use( express.static( `${__dirname}/nationchains/tribes/${conf.mayorId}/www/cdn/public`, { dotfiles: 'allow' } ) ); */ // Routers add any routes from /routes and /plugins let logroute = "Routes available on this apxtrib instance: "; routes.forEach((r) => { try { logroute += r.url + "|" + r.route; app.use(r.url, require(r.route)); } catch (err) { logroute += " (err check it)"; console.log("raise err-:", err); } }); console.log(logroute); if (infotown.townId == "devfarm") { console.log( `\x1b[42m############################################################################################\x1b[0m\n\x1b[42mThis is dev conf to switch this as production, you must run:\n 1 - 'yarn dev nationId:ants townId:usbfarm dns:usbfarm-ants ' to conf your town and check it.\n 2 - 'yarn startpm2'\n Where:\n\x1b[42m * nationId have to exist in the nationchains\n * townId new or if exist must have the smae current dns,\n * dns domaine that has to redirect 80/443 into this server (example wall-ants.ndda.fr redirect to 213.32.65.213 ).\n Check README's project to learn more.\x1b[0m\n\x1b[42m############################################################################################\x1b[0m` ); } app.listen(conf.api.port, () => { let webaccess = `check in your browser that api works`; conf.dns.forEach((u) => { webaccess += `http://${u}:${conf.api.port}`; }); console.log(webaccess); }); console.log( "\x1b[42m\x1b[37m", "Made with love for people's freedom, enjoy !!!", "\x1b[0m" );