adminapi/apxtri/apxtri.js
2024-12-02 17:24:21 +01:00

362 lines
13 KiB
JavaScript
Executable File

//const { argv } = require("process");
const fs = require("fs-extra");
const bodyParser = require("body-parser");
const glob = require("glob");
const path = require("path");
const Mustache = require("mustache");
const cors = require("cors");
const express = require("express");
const process = require("process");
/*******************************************
SEE README.md to start
********************************************/
const apxtri = {};
apxtri.main = async () => {
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(0);
}
//Check prerequest data
if (
fs.existsSync("../adminapi/objects/tribes/idx/tribes_dns.json") &&
fs.existsSync("../adminapi/objects/tribes/itm/adminapi.json")
) {
// check all tribes are in tribes_dns.json
const conf = fs.readJSONSync(
"../adminapi/objects/tribes/itm/adminapi.json"
);
const tribesdns = fs.readJsonSync(
`../adminapi/objects/tribes/idx/tribes_dns.json`
);
//check if new tribe was add
glob
.sync("../*")
.filter((f) => fs.lstatSync(f).isDirectory())
.forEach(async (t) => {
const tribe = path.basename(t);
//console.log(tribe);
if (![".", ".."].includes(tribe) && !tribesdns[tribe]) {
await apxtri.setuptribe(tribe, conf);
}
});
} else {
const initconf = fs.readJsonSync(
"../adminapi/apxtri/setup/initadminapi.json"
);
await apxtri.setuptribe("adminapi", initconf);
}
// run api with update conf
apxtri.runexpress(
fs.readJsonSync(`../adminapi/objects/tribes/idx/tribes_dns.json`),
fs.readJSONSync("../adminapi/objects/tribes/itm/adminapi.json")
);
};
apxtri.getip = async () => {
/**
* Return json with public IP and network interfaces with their local IP
* {WANIP:"public IP","eth0":"localIPoneth0",...}
*/
const network = {};
const urlgetip = `https://api.ipify.org?format=json`;
const getdata = await fetch(urlgetip);
if (getdata.ok) {
const data = await getdata.json();
network.WANIP = data.ip;
}
const os = await import("os");
const interfaces = os.networkInterfaces();
for (const name in interfaces) {
for (const iface of interfaces[name]) {
// Check for IPv4 and make sure it's not an internal (loopback) address
if (iface.family === "IPv4" && !iface.internal) {
network[name] = iface.address;
//localIP = iface.address;
//console.log(`Local network IP (${name}): ${localIP}`);
}
}
}
return network;
};
apxtri.setuptribe = async (tribe, conf) => {
let inittribe;
if (tribe == "adminapi") {
console.log(
"Nice to meet you, this is a first install hope you'll enjoy, if any issues please request on discord https://discord.gg/jF7cAkZn"
);
try {
inittribe = conf;
inittribe.townpath = __dirname.replace("/adminapi/apxtri", "");
const townnation = inittribe.townpath.split("/").slice(-1)[0].split("-");
inittribe.townId = townnation[0];
inittribe.nationId = townnation[1];
inittribe.dns.push(
`admin.adminapi.${inittribe.townId}.${inittribe.nationId}`
);
} catch (err) {
console.log("Your town folder must be something townid-nation");
}
} else {
console.log(`a new tribe called ${tribe} was detected`);
const adminapiconf = fs.readJSONSync(
"../adminapi/objects/tribes/itm/adminapi.json"
);
inittribe = {
tribeId: tribe,
townpath: __dirname.replace("/adminapi/apxtri", ""),
dns: [`admin.${tribe}.${conf.townId}.${conf.nationId}`],
status: "unchain",
nationId: conf.nationId,
townId: conf.townId,
activelog: [],
api: { port: adminapiconf.api.port },
socket: { port: adminapiconf.socket.port },
};
}
inittribe.sudoUser = process.env.USER;
//check nation exist and town does not exist
if (
!fs.existsSync("../adminapi/objects/nations/idx/lst_nationId.json") ||
!fs.existsSync("../adminapi/objects/towns/idx/lst_townId.json")
) {
console.log(
`Sorry, check setup.sh process that was not able to init your adminapi/objects `
);
process.exit(0);
}
fs.outputJSONSync(`../adminapi/objects/tribes/itm/${tribe}.json`, inittribe, {
space: 2,
});
if (!fs.existsSync("../adminapi/objects/tribes/conf.json")) {
fs.outputJSONSync("../adminapi/objects/tribes/conf.json", {
name: "tribes",
schema: "adminapi/schema/tribes.json",
lastupdate: 0,
});
}
const lst_tribeIdpath = "../adminapi/objects/tribes/idx/lst_tribeId.json";
let lsttribe = fs.existsSync(lst_tribeIdpath)
? fs.readJsonSync(lst_tribeIdpath)
: [];
lsttribe.push(tribe);
fs.outputJSONSync(lst_tribeIdpath, lsttribe);
const tribes_dnspath = "../adminapi/objects/tribes/idx/tribes_dns.json";
const tribedns = fs.existsSync(tribes_dnspath)
? fs.readJsonSync(tribes_dnspath)
: {};
tribedns[tribe] = inittribe.dns;
fs.outputJSONSync(tribes_dnspath, tribedns);
const tribespath = "../adminapi/objects/tribes/idx/tribes.json";
const tribes = fs.existsSync(tribespath) ? fs.readJSONSync(tribespath) : {};
tribes[tribe] = inittribe;
fs.outputJSONSync(tribespath, tribes, { space: 2 });
// check nginx conf and eventually change if not starting by user "current user";
let etcnginx = fs.readFileSync("/etc/nginx/nginx.conf", "utf8");
if (etcnginx.split("\n")[0] !== `user ${inittribe.sudoUser};`) {
inittribe.mainpath = inittribe.townpath.replace(
`/${inittribe.townId}-${inittribe.nationId}`,
""
);
const nginxmain = fs.readFileSync(
"../adminapi/apxtri/setup/nginx.maincf",
"utf8"
);
console.log("Modify /etc/nginx/nginx.conf");
fs.outputFileSync(
"/etc/nginx/nginx.conf",
Mustache.render(nginxmain, inittribe),
{ asAdmin: true }
);
}
// add conf for http://adminapx.adminapi
const nginxapx = fs.readFileSync(
"../adminapi/apxtri/setup/nginx.wwwscf",
"utf8"
);
inittribe.website = "admin";
fs.outputFileSync(
`../${tribe}/nginx/${inittribe.website}.${tribe}.${inittribe.townId}.${inittribe.nationId}.conf`,
Mustache.render(nginxapx, inittribe)
);
fs.outputFileSync(
`../${tribe}/objects/wwws/admin/dist/index_en.html`,
`<h1>Wellcome in ${tribe}</h1>`,
"utF8"
);
// add hosts entry for local access
// this command is ran by the setup.sh
// grep -q '^127.0.0.1 adminapx.adminapi' /etc/hosts || echo '127.0.0.1 adminapx.adminapi' | sudo tee -a /etc/hosts > /dev/null
const ips = await apxtri.getip();
const nginxrestart =
inittribe.nginx && inittribe.nginx.restart
? inittribe.nginx.restart
: fs.readJSONSync("../adminapi/apxtri/setup/initadminapi.json").nginx
.restart;
const { exec } = require("child_process");
exec(nginxrestart, (error, stdout, stderr) => {
if (error) {
console.log("\x1b[42m", error, stdout, stderr, "x1b[0m");
process.exit(0);
} else {
const etchosts = Object.values(ips)
.map((ip) => `${ip} ${inittribe.dns.join(" ")}`)
.join("\n ");
console.log(
`\x1b[42m###############################################################################################################\x1b[0m\n\x1b[42mWellcome into this fresh apxtri install, currently running as "$ yarn dev".\n To access and set up a public domain name, add lines in file /etc/hosts into the machine you want to use to access:\n\x1b[0m\x1b[32m ${etchosts} \x1b[0m \x1b[42m\n Then open in your local browser \x1b[0m\x1b[32m http:// ${inittribe.dns.join(
" "
)} \x1b[0m \x1b[42m \nFor local dev continue with 'yarn dev'\nTo run as a production run 'yarn startapx' or 'yarn restartapx' then pm2 monitor your production process.\n\x1b[0m\n\x1b[42m###############################################################################################################\x1b[0m`
);
}
});
};
apxtri.runexpress = async (tribesdns, conf) => {
const Odmdb = require(path.resolve("./apxtri/models/Odmdb.js"));
let tribeIds = Object.keys(tribesdns);
// context is store in /itm/tribename.json ={contexte:{routes:[],models:[{model:,tplstringslg:[]}]}
// 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)
let routes = [];
let doms = [];
tribeIds.forEach((t) => {
tribesdns[t].forEach((d) => {
const dm = d.split(".").slice(-2).join(".");
if (!doms.includes(dm)) doms.push(dm);
//reindex database attention check dev-ants/.. a bug was fixed
glob.sync(`../${t}/objects/*`).forEach((o) => {
//console.log("reindex: ", o);
Odmdb.runidx(o);
});
});
const context = {};
const pathtr = path.resolve(`../${t}`);
context.routes = [];
tribroutes = glob.sync(`${pathtr}/apxtri/routes/*.js`).map((f) => {
const rt = `/${t}/${path.basename(f, ".js")}`;
context.routes.push(rt);
return { url: rt, route: f };
});
context.models = glob.sync(`${pathtr}/apxtri/models/*.js`).map((f) => {
const modname = `${path.basename(f, ".js")}`;
return {
model: modname,
tplstrings: glob
.sync(`${pathtr}/objects/tplstrings/${modname}_*.json`)
.map((l) => path.basename(l, ".json").split("_")[1]),
};
});
//console.log(context.routes);
//console.log(context.models);
//const conft = `../itm/${t}.json`;
//const ctx = fs.readJsonSync(conft);
//ctx.context = context;
//fs.outputJSONSync(conft, ctx, { spaces: 2 });
routes = routes.concat(tribroutes);
});
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
tribeIds.forEach((t) => {
app.use(`/${t}`, bodyParser.urlencoded(conf.api.bodyparse.urlencoded));
app.use(`/${t}`, bodyParser.json(conf.api.bodyparse.json));
// To set depending of post put json data size to send
app.use(`/${t}`, express.json(conf.api.json));
});
app.disable("x-powered-by"); // for security
app.locals.tribeids = tribeIds;
const currentmod = "apxtri";
const log = conf.api.activelog
? conf.api.activelog.includes(currentmod)
: false;
console.log(
currentmod,
" Allowed DOMs to access to this apxtri server:",
JSON.stringify(doms)
);
console.log(currentmod, " app.locals.tribeids", app.locals.tribeids);
// Cors management
let regtxt = "(test";
doms.forEach((d) => {
regtxt += `|${d.replace(/\./g, "\\.")}`;
});
regtxt += ")$";
// let cor = false;whatwg-url
const regorigin = new RegExp(regtxt);
app.use((req, res, next) => {
let cor = false;
//console.log(req.headers)
if (req.headers.origin == undefined) {
//used for mobile access
cor = true;
} else {
cor = regorigin.test(req.headers.origin);
}
if (!cor)
console.log(
`The domain name ${req.headers.origin} is not allow to access for CORS settings, add it in itm/tribename.json in dns current origin allow are filter by ${regtxt}`
);
cors({
origin: cor,
allowedHeaders: conf.api.exposedHeaders,
exposedHeaders: conf.api.exposedHeaders,
credentials: true,
preflightContinue: false,
optionsSuccessStatus: 204,
});
next();
});
// Routers add any routes from /routes and /plugins
let logroute = "Routes available on this apxtri instance: \n";
routes.forEach((r) => {
try {
logroute += r.url.padEnd(30, " ") + r.route + "\n";
app.use(r.url, require(r.route));
} catch (err) {
logroute += " (err check it module.exports=router;? or ...)\n======\n ";
console.log("raise err-:", err);
}
});
if (log) {
console.log(currentmod, logroute);
console.log(currentmod, conf.api);
}
//Listen event file for each tribe
// @TODO à ajouter ici
const ips = await apxtri.getip();
app.listen(conf.api.port, () => {
let webaccess = `/api/ waits request on port:${conf.api.port} `;
let localnet = "/etc/hosts for your local network:\n";
let publicnet = "/etc/hosts for internet network:\n";
conf.dns.forEach((u) => {
//webaccess += `http://${u}/api/ `;
Object.keys(ips).forEach((ik) => {
if (ik == "WANIP") {
publicnet += `${ips.WANIP} ${u} \n`;
} else {
localnet += `${ips[ik]} ${u} \n`;
}
});
});
console.log(
`\x1b[42m\x1b[37m${webaccess} \nOpen in your browser http(s):// ${conf.dns.join(
" "
)} to manage this apXtri town. \nCheck your network conf \n${localnet} ${publicnet}\nMore in README's project.\nTo get support ask \x1b[0m\x1b[32m in discord https://discord.gg/jF7cAkZn `
);
console.log(
"\x1b[42m\x1b[37m \n",
" Made with love for people's freedom, enjoy !!! ",
"\x1b[0m"
);
});
};
apxtri.main();