maj setup
This commit is contained in:
parent
85862577b2
commit
908daaa8d3
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,4 @@
|
||||
/node_modules
|
||||
/nationchains
|
||||
/adminapi/www/nginx_adminapx.conf
|
||||
/adminapi/www/adminapx/conf/setup_xx.json
|
||||
/yarn*
|
||||
|
68
adminapi/bash/sftpaccounttotribe.sh
Normal file
68
adminapi/bash/sftpaccounttotribe.sh
Normal file
@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
# apXtrib
|
||||
# @param action townId nationId tribeId appname user
|
||||
# @return a symlink into /home/tribeId/www/appname to ~/workspace/townId-nationId/tribes/tribeId/www/appname
|
||||
# @return a user is create to access in sftp the jail directory /home/tribeId/www/appname
|
||||
#
|
||||
# ./sftpaccounttotribe.sh wall ants smatchit smatchapp sagark
|
||||
|
||||
@todo A faire
|
||||
echo $1;
|
||||
echo $2;
|
||||
echo $3;
|
||||
echo $4;
|
||||
echo $5;
|
||||
echo $6;
|
||||
|
||||
create_sshspace () {
|
||||
# To activate sftp only into towniD-natinId/tribes/tribeId/www
|
||||
# Create a linux account with the name tribeId
|
||||
# $ sudo useradd tribeId
|
||||
# $ passwd tribeId
|
||||
# $ cd ~/workspace/towniD-natinId/tribes/tribeId/www
|
||||
# copy and past /bin dev/ /etc/ lib/ lib64/
|
||||
# in /bin cp /bin/bash or /bin/sh depending of /etc/passwd SHELL (check /etc/default/useradd to change if needed)
|
||||
# to check dependance $ ldd /bin/bash
|
||||
# cp all /lib/x... and all /lib64/ into ./ (add same folder name,...)
|
||||
# in /dev :
|
||||
# $ sudo mknod -m 666 null c 1 3;sudo mknod -m 666 tty c 5 0; sudo mknod -m 666 zero c 1 5; sudo mknod -m 666 random c 1 8;
|
||||
# check they are root $ ls -ld ../ if not $ chown -R root:root /dev
|
||||
# $ chmod -R 0755 /dev
|
||||
#$ sudo cp /etc/passwd ./etc/ Need to do each new user
|
||||
#$ sudo cp /etc/group ./etc/ Need to do each new user
|
||||
|
||||
|
||||
|
||||
#sudo mkdir -p /home/tribeswww/smatchit/smatchapp
|
||||
# cd /home/tribeswww/smatchit/smatchapp
|
||||
# sudo vim
|
||||
|
||||
# sudo mknod -m 666 null c 1 3;sudo mknod -m 666 tty c 5 0; sudo mknod -m 666 zero c 1 5; sudo mknod -m 666 random c 1 8;
|
||||
# sudo chown root:root /home/tribeswww/smatchit/smatchapp
|
||||
# sudo chmod 0755 /home/tribeswww/smatchit/smatchapp
|
||||
# sudo mkdir -p /home/tribeswww/smatchit/smatchapp/bin
|
||||
# sudo cp -v /bin/bash /home/tribeswww/smatchit/smatchapp/bin/
|
||||
# sudo mkdir -p /home/tribeswww/smatchit/smatchapp/lib64
|
||||
# check ldd /bin/bash
|
||||
# sudo cp -v /lib64/{libtinfo.so.5,libdl.so.2,libc.so.6,ld-linux-x86-64.so.2} /home/tribeswww/smatchit/smatchapp/lib64/
|
||||
# sudo useradd smatchapp;
|
||||
# sudo passwd smatchapp;
|
||||
# sudo mkdir /home/tribeswww/smatchit/smatchapp/etc
|
||||
# sudo cp -vf /etc/{passwd,group} /home/tribeswww/smatchit/smatchapp/etc/ (this have to be done each time a new user is create)
|
||||
#sudo vim /etc/ssh/sshd_config
|
||||
|
||||
}
|
||||
|
||||
create_user (){
|
||||
#
|
||||
#
|
||||
#
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
'add')
|
||||
echo ""
|
||||
create_user $5
|
||||
;;
|
||||
esac
|
||||
|
@ -1 +0,0 @@
|
||||
["https://wallants.ndda.fr/nations/synchro"]
|
@ -1,8 +1,7 @@
|
||||
{
|
||||
"dns": ["adminapx"],
|
||||
"dns": ["devfarm-ants"],
|
||||
"towns": [
|
||||
{ "townId": "wall", "nationId": "ants", "url": "wall-ants.ndda.fr" },
|
||||
{ "townId": "hill", "nationId": "ants", "url": "hill-ants.ndda.fr" }
|
||||
{ "townId": "wall", "nationId": "ants", "dns": "wall-ants.ndda.fr" }
|
||||
],
|
||||
"api": {
|
||||
"port": 3020,
|
||||
|
@ -1,49 +1,49 @@
|
||||
server {
|
||||
server_name {{#dns}} {{.}} {{/dns}};
|
||||
access_log {{{nginx.logs}}}.access.log main;
|
||||
server_name {{#dns}} {{.}} {{/dns}};
|
||||
access_log {{{nginx.logs}}}.access.log main;
|
||||
|
||||
location ~* /nationchains/(schema|blocks|pagans|towns|nations)/ {
|
||||
# Warning: never add tribes for keeping it private
|
||||
root {{{dirapi}}}/;
|
||||
}
|
||||
|
||||
# /plugins/pluginame/components/xxx?plugin=pluginname&pluginkey=key
|
||||
# acess if exist pluginkey
|
||||
location /plugins/ {
|
||||
add_header X-debug "plugins local $arg_plugin/keys/$arg_pluginkey sent";
|
||||
root {{{nginx.fswww}}}/plugins/;
|
||||
if (-f {{{nginx.fswww}}}/plugins/$arg_plugin/keys/$arg_pluginkey) {
|
||||
rewrite /plugins/([^/]+)/components/([^\?]+) /$1/components/$2 break;
|
||||
}
|
||||
return 403 "No valid token access for plugin:$arg_plugin with token:$arg_pluginkey please ask your admin";
|
||||
}
|
||||
|
||||
location /cdn/ {
|
||||
rewrite /cdn/(.*$) /$1 break;
|
||||
root {{{nginx.fswww}}}www/cdn/;
|
||||
}
|
||||
|
||||
location /spacedev/ {
|
||||
rewrite /spacedev/(.*$) /$1 break;
|
||||
root {{{nginx.fswww}}}spacedev/{{{nginx.website}}}/dist/;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
rewrite /api/(.*$) /$1 break;
|
||||
proxy_pass http://localhost:{{{api.port}}};
|
||||
proxy_redirect off;
|
||||
include proxy_params;
|
||||
}
|
||||
|
||||
location / {
|
||||
root {{{nginx.fswww}}}/{{{nginx.website}}};
|
||||
index index.html {{{nginx.pageindex}}};
|
||||
}
|
||||
error_page 404 /404.html;
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/local/nginx/html;
|
||||
}
|
||||
location ~* /nationchains/(schema|blocks|pagans|towns|nations)/ {
|
||||
# Warning: never add tribes for keeping it private
|
||||
root {{{dirapi}}}/;
|
||||
}
|
||||
|
||||
# /plugins/pluginame/components/xxx?plugin=pluginname&pluginkey=key
|
||||
# acess if exist pluginkey
|
||||
location /plugins/ {
|
||||
add_header X-debug "plugins local $arg_plugin/keys/$arg_pluginkey sent";
|
||||
root {{{nginx.fswww}}}/plugins/;
|
||||
if (-f {{{nginx.fswww}}}/plugins/$arg_plugin/keys/$arg_pluginkey) {
|
||||
rewrite /plugins/([^/]+)/components/([^\?]+) /$1/components/$2 break;
|
||||
}
|
||||
return 403 "No valid token access for plugin:$arg_plugin with token:$arg_pluginkey please ask your admin";
|
||||
}
|
||||
|
||||
location /cdn/ {
|
||||
rewrite /cdn/(.*$) /$1 break;
|
||||
root {{{nginx.fswww}}}/cdn/;
|
||||
}
|
||||
|
||||
location /spacedev/ {
|
||||
rewrite /spacedev/(.*$) /$1 break;
|
||||
root {{{nginx.fswww}}}spacedev/{{{nginx.website}}}/dist/;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
rewrite /api/(.*$) /$1 break;
|
||||
proxy_pass http://localhost:{{{api.port}}};
|
||||
proxy_redirect off;
|
||||
include proxy_params;
|
||||
}
|
||||
|
||||
location / {
|
||||
root {{{nginx.fswww}}}/{{{nginx.website}}};
|
||||
index index.html {{{nginx.pageindex}}};
|
||||
}
|
||||
error_page 404 /404.html;
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/local/nginx/html;
|
||||
}
|
||||
}
|
1
adminapi/www/adminapx/conf/setup_xx.json
Normal file
1
adminapi/www/adminapx/conf/setup_xx.json
Normal file
@ -0,0 +1 @@
|
||||
{"nationId":"ants","townId":"devfarm","dns":["devfarm-ants"],"comment":"Auto generate setup from apxtrib after node apxtrib nationId:value townId:value dns:domaine_to_access"}
|
@ -31,7 +31,7 @@
|
||||
<script src="cdn/share/js/openpgp.min.js"></script>
|
||||
<script src="cdn/share/js/mustache.min.js"></script>
|
||||
<script src="cdn/share/js/axios.min.js"></script>
|
||||
<script src="static/js/apxtribcli.js"></script>
|
||||
<script src="static/js/apx.js"></script>
|
||||
<script src="static/js/apxpagans.js"></script>
|
||||
<script src="static/js/apxtribes.js"></script>
|
||||
<script src="static/js/apxtowns.js"></script>
|
||||
@ -47,7 +47,7 @@
|
||||
<body>
|
||||
|
||||
<div id="apxtitle" class="p-5 bg-primary text-white text-center" apptoload="app.load('apxtitle','title','setup')"
|
||||
add2data tpldata="static/tpldata/setup_xx.json">
|
||||
add2data tpldata="conf/setup_xx.json">
|
||||
<h1>apXtrib</h1>
|
||||
<p> Manage and understand apXtrib back-end</p>
|
||||
</div>
|
||||
@ -88,8 +88,8 @@
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button"
|
||||
aria-expanded="false">Mayor's Town</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" add2data tpl="static/tpl/townsetup_en.mustache"
|
||||
onclick="app.load('apxmain','townsetup','setup')">Chain a town</a></li>
|
||||
<li><a class="dropdown-item" add2data tpl="static/tpl/townowner_en.mustache"
|
||||
onclick="app.load('apxmain','townowner',towns.loadtpldata())">Own this town</a></li>
|
||||
<li><a class="dropdown-item" href="#">Manage Tribes</a></li>
|
||||
<li><a class="dropdown-item" href="#">Backup Town/Tribes</a></li>
|
||||
<li>
|
||||
@ -126,7 +126,8 @@
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#">Odmdb Schema</a></li>
|
||||
<li><a class="dropdown-item" href="#">Odmdb CRUD</a></li>
|
||||
<li><a class="dropdown-item" href="#">Host a web app</a></li>
|
||||
<li><a class="dropdown-item" add2data tpl="static/tpl/devophostawebapp_en.mustache"
|
||||
onclick="app.load('apxmain','devophostawebapp',tribes.loadtpldata())">Host a web app</a></li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
|
@ -1,41 +1,33 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "socialworld/objects/schema/towns",
|
||||
"$id": "schema/towns",
|
||||
"title": "Town",
|
||||
"description": "A town belonging to a nation from apXtrib world",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"townId": {
|
||||
"description": "|Towns|townnamedesc",
|
||||
"desclong": "|Townss|townnamedesclong",
|
||||
"info": "|Towns|townnameinfo",
|
||||
"type": "string",
|
||||
"pattern":"^[a-z0-9]*$"
|
||||
"pattern": "^[a-z0-9]*$"
|
||||
},
|
||||
"nationId": {
|
||||
"description": "|Towns|nationdesc",
|
||||
"desclong": "|Townss|nationdesclong",
|
||||
"type": "string",
|
||||
"$apxenumkey": "socialworld/objects/nations/searchindex/nations_uuid_uuid.json"
|
||||
},
|
||||
"mayorId": {
|
||||
"type": "string",
|
||||
"$apxenumkey": "socialworld/objects/nations/searchindex/nations_uuid_uuid.json"
|
||||
},
|
||||
"status": {
|
||||
"desc": "|Towns|statusdesc",
|
||||
"default": "active",
|
||||
"type": "string",
|
||||
"$apxenumkey": "data",
|
||||
"data": {
|
||||
"chain": { "desc": "|Towns|statuschain" },
|
||||
"tochain": { "desc": "|Towns|statustosync" },
|
||||
"unchain": { "desc": "|Towns|statusunchain" }
|
||||
"enum":["chain","unchain","tochain"]
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"desc": "|Towns|urldesc",
|
||||
"type": "string",
|
||||
"apxtype":"url"
|
||||
"dns": {
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": ["townId", "status", "nationId", "url"],
|
||||
"required": ["townId", "status", "nationId", "dns"],
|
||||
"apxprimarykey": "townId",
|
||||
"apxsearchindex": [
|
||||
{ "key": "status", "value": "townId" },
|
||||
|
@ -4,6 +4,19 @@
|
||||
"use strict";
|
||||
var towns = towns || {};
|
||||
|
||||
towns.lauch =()=>{
|
||||
|
||||
}
|
||||
towns.loadtpldata = () => {
|
||||
// adapte tpldata to template tpl
|
||||
const dataowner = apx.data.tpldata.setup;
|
||||
dataowner.alias=apx.data.headers.xalias;
|
||||
dataowner.auth = dataowner.alias=="anonymous";
|
||||
dataowner.devtown = dataowner.townId == "devfarm";
|
||||
if (dataowner.mayorid) dataowner.owner = dataowner.mayorid == dataowner.alias;
|
||||
console.log('Data return to template',dataowner)
|
||||
return dataowner;
|
||||
};
|
||||
towns.owntown = () => {
|
||||
|
||||
|
||||
// only the owner can give ownership to someone else
|
||||
|
||||
};
|
||||
|
@ -0,0 +1,19 @@
|
||||
/*eslint no-undef:0*/
|
||||
/*eslint-env browser*/
|
||||
|
||||
"use strict";
|
||||
var tribes = tribes || {};
|
||||
|
||||
tribes.loadtpldata = () => {
|
||||
// adapte tpldata to template tpl
|
||||
const datahost = apx.data.tpldata.setup;
|
||||
datahost.offers=[{offerid:"Z",title:" Offre gratuite Max space 1Mo public"},{offerid:"A",title:" Max space 5Mo public"},{offerid:"B",title:" Max space 5Mo private for less than 100 Persons"},{offerid:"C",title:" Max space 500 Mo private for less than 100 Persons"},{offerid:"D",title:" Max space 2 Go private for less than 1000 Persons"}],
|
||||
datahost.alias=apx.data.headers.xalias;
|
||||
datahost.auth = datahost.alias=="anonymous";
|
||||
console.log('Data return to template',datahost)
|
||||
return datahost;
|
||||
};
|
||||
tribes.addspaceweb = ( offer,domain, appname,msg) => {
|
||||
// Request to a druid to host space web accessible from a domain to share tribes data /persons / ...
|
||||
alert('Under construction to send notification to druid to get an access')
|
||||
};
|
@ -0,0 +1,57 @@
|
||||
<h4>Host a web app</h4>
|
||||
<p> A druid has to create a webapp space into his tribe/www/webappname. To do that he needs:</p>
|
||||
<ul>
|
||||
<li> a domain name that redirect to the IP of this town</li>
|
||||
<li> Define which method to push content, by simply using the upload browser or by creating a linux account to connect by sftp and mount it in distance machine.</li>
|
||||
</ul>
|
||||
<p>Option sftp: as sudo</p>
|
||||
<code>
|
||||
$ sudo useradd -s /bin/bash -m -d /home/sagark -c "sagark" sagark
|
||||
$ sudo paswd sagark to set a robust password
|
||||
$ chmod o+rwx folderappname # workspace/townId-nationId/tribes/tribeId/www/appname
|
||||
# su sagark
|
||||
$ mkdir -p --mode 700 ~/tribes/smatchit/www
|
||||
$ ln -s /home/phil/workspace/wall-ants/tribes/smatchit/www/smatchapp ~/tribes/smatchit/www/smatchapp
|
||||
# su sudoer to add file to jail user in his home
|
||||
$ sudo mkdir -p /home/sagark/dev
|
||||
$ cd /home/sagark/dev
|
||||
$ sudo mknod -m 666 null c 1 3
|
||||
$ sudo mknod -m 666 tty c 5 0
|
||||
$ sudo mknod -m 666 zero c 1 5
|
||||
$ sudo mknod -m 666 random c 1 8
|
||||
# change access right
|
||||
|
||||
$ userdel -r sagark
|
||||
</code>
|
||||
{{^auth}}
|
||||
<p><b>You have to be authenticate on this town with accessright on tribe to request a new web space for this tribe.</b></p>
|
||||
{{/auth}}
|
||||
{{#auth}}
|
||||
<p> Send him a request to create as alias:{{alias}}</p>
|
||||
<div class="col-md-6">
|
||||
<label for="inputdomain" class="form-label">A domain</label>
|
||||
<input type="text" class="form-control" id="inputdomain" placeholder="A register domain that redirect to the town IP">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="inputappname" class="form-label">Name of your app</label>
|
||||
<input type="text" class="form-control" id="inputappname" placeholder="folder name in www/ and name of xapp in header">
|
||||
</div>
|
||||
<div id="selectoffer" class="">
|
||||
<label for="selectoffer" class="col-12 col-form-label">Choose your offer</label>
|
||||
<div class="col-12">
|
||||
<select class="form-select" id="chooseoffer" aria-label="" placeholder="To get a hosting web place">
|
||||
{{#offers}}
|
||||
<option {{#selected}}selected{{/selected}} value="{{offerid}}">{{title}}</option>
|
||||
{{/offers}}
|
||||
</select>
|
||||
<input class="d-none" id="inputofferid" value="{{offerid}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="msgtodruid" class="form-label">Message</label>
|
||||
<textarea rows="5" id="msgtodruid" class="form-control" placeholder="A message to tribe's druid about hosting"></textarea>
|
||||
</div>
|
||||
<button class="btn btn-primary"
|
||||
onclick="tribes.addspaceweb(document.getElementById('inputofferid').value,document.getElementById('inputdomain').value,document.getElementById('inputappname').value,document.getElementById('msgtodruid').value);">
|
||||
Request a space web</button>
|
||||
{{/auth}}
|
@ -77,7 +77,7 @@
|
||||
onclick="delete apx.data.auth;apx.data.headers=apxlocal.headers;apx.save();alert('delete apx.data.auth and reinit apx.data.header')">
|
||||
Remove headers</button>
|
||||
<hr>
|
||||
<h3>I proove that i own this alias</h3>
|
||||
<h3>I prove that i own this alias</h3>
|
||||
<div class="col-md-6">
|
||||
<label for="inputaliasauth" class="form-label">Your alias</label>
|
||||
<input type="text" class="form-control" id="inputaliasauth" placeholder="A public alias that any one see">
|
||||
|
46
adminapi/www/adminapx/static/tpl/townowner_en.mustache
Normal file
46
adminapi/www/adminapx/static/tpl/townowner_en.mustache
Normal file
@ -0,0 +1,46 @@
|
||||
<div class="container">
|
||||
{{^auth}}
|
||||
<p> Sorry you need to have a valid authentification that prove that you are the major of this town to go ahead</p>
|
||||
{{/auth}}
|
||||
{{#auth}}
|
||||
<div class="row">
|
||||
<h4>Current setup</h4>
|
||||
<p>Your current town name: <b>{{townId}}</b> is link to nation: <b>{{nationId}}</b> and accessible with domain <b>{{#dns}}{{.}}{{/dns}}</b>.</p>
|
||||
{{#devtown}}
|
||||
<p> <b>devfarm</b> is a dev town that cannot be chain, to start dev means:</p>
|
||||
<ul>
|
||||
<li> install process made http://devfarm-ants available on your machine the /adminapi/www/adminapx/index_en.html to manage this dev town</li>
|
||||
<li>if not working, check that your /etc/hosts contain 127.0.0.1 devfarm-ants</li>
|
||||
<li>start the api in apxtrib folder $ yarn dev to allow manager adminapx to act on this trib</li>
|
||||
</ul>
|
||||
<p>Then you can dev in apxtrib to send your release to the project leader.</p>
|
||||
<p>You can dev some app as a tribe level into /devfarm-ants see menu Mayor's Town/Manage Tribes to create a dev tribe. You can unzip a tribe into a folder for debuging or dev with real backup data.</p>
|
||||
{{/devtown}}
|
||||
{{^mayorId}}
|
||||
<p>
|
||||
This town is not own by a mayor, anyone can use a pagan account to tell that he is owning this, so please,
|
||||
{{#auth}} you are authenticate under alias: {{alias}} <button class="btn btn-primary" onclick="towns.owntown();">Own this town</button>
|
||||
{{/auth}}
|
||||
{{^auth}} you have to be authenticat with an alias to own this town. go to Pagan / create login logout and authentify yourself and come back here.
|
||||
{{/auth}}
|
||||
{{/mayorId}}
|
||||
{{#owner}}
|
||||
<h4> Own & chain a town</h4>
|
||||
{{#devtown}}
|
||||
<p>You need to change town name (by default it is for dev "devfarm") by running on serveur as sudoer user: </p>
|
||||
<code>node apxtrib.js nationId:nationRequested townId:nameNotalreadyUse dns:domainToAccessTown </code>
|
||||
<p>Come back here.</p>
|
||||
{{/devtown}}
|
||||
{{^devtown}}
|
||||
<p> As owner you({{alias}}) can give the ownership of this town to an other alias. Be carefull when you click on this button you lose all your accessright on this town.</p>
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-control" placeholder="Give alias to take the ownership" aria-label="New owner alias" aria-describedby="button-chgown">
|
||||
<button class="btn btn-outline-secondary" type="button" id="button-chgown">Give him the role on this town</button>
|
||||
</div>
|
||||
|
||||
|
||||
{{/devtown}}
|
||||
{{/owner}}
|
||||
</div>
|
||||
{{/auth}}
|
||||
</div>
|
@ -1,50 +0,0 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<h4> Setup a new town</h4>
|
||||
<p>This form let you start joining a nation.</p>
|
||||
<p>For dev you can stay like this and use http instead of https. To join a nation you need:</p>
|
||||
<ul>
|
||||
<li> Get a domain name register to a publicIP that route web traffic from 80 and 443 to this machine</li>
|
||||
<li> Get a pagan identity and be authenticated <a onclick="app.load('apxmain','pagancreate',{})"> click here to create or authentify yoursefl</a>
|
||||
<li> Synchronize the nations, to update your nationchains (carefful all your local stuff will be deleted)</li>
|
||||
<li> Ready to your new mayor role of this town</li>
|
||||
<li> Start saling your hosting</li>
|
||||
</ul>
|
||||
<div class="col-sm-2">
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<div class="mb-3 row">
|
||||
<label for="selectnationid" class="col-sm-6 col-form-label">Select the nation to join</label>
|
||||
<div class="col-sm-6">
|
||||
<select class="form-select" data-nationId="{{nationId}}" aria-label="" placeholder="A nation">
|
||||
{{#nations}}
|
||||
<option {{#selected}}selected{{/selected}} value="{{nationId}}">{{nationId}}</option>
|
||||
{{/nations}}
|
||||
</select>
|
||||
<input class="d-none" id="inputnationId" value="{{nationId}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label for="inputtownid" class="col-sm-6 col-form-label">Your Town</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" value="{{townId}}" class="form-control" id="inputtownid">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label for="inputtribeid" class="col-sm-6 col-form-label">Your Tribes</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" value="{{tribeId}}" class="form-control" id="inputreibeid">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label for="inputdnstown" class="col-sm-6 col-form-label">Domain name of your town (to access this app from the web)</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" value="{{dns}}" class="form-control" id="inputdnstown">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button onclick="setup.lauchtown(document.getElementById('inputnationId').value, document.getElementById('inputtownId').value,document.getElementById('inputdns').value)" class="btn btn-primary mb-3">Launch this town</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -3,12 +3,12 @@ const fs = require("fs-extra");
|
||||
const glob = require("glob");
|
||||
const jwt = require("jwt-simple");
|
||||
const axios = require("axios");
|
||||
const path=require('path');
|
||||
const conf=require(`${process.env.dirtown}/conf.json`);
|
||||
const Odmdb = require('./Odmdb.js');
|
||||
const path = require("path");
|
||||
const conf = require(`${process.env.dirtown}/conf.json`);
|
||||
const Odmdb = require("./Odmdb.js");
|
||||
// lowercase 1st letter is normal
|
||||
const towns = require('./Towns.js');
|
||||
const pagans = require('./Pagans.js');
|
||||
const towns = require("./Towns.js");
|
||||
const pagans = require("./Pagans.js");
|
||||
/*
|
||||
Blockchain manager
|
||||
* Manage network directory of nations and towns
|
||||
@ -23,68 +23,96 @@ Nations.init = () => {
|
||||
console.group("init Nations");
|
||||
};
|
||||
|
||||
Nations.updateChains = async (newtown) =>{
|
||||
Nations.chaintown = (nationId, townId) => {
|
||||
/**
|
||||
* @newtown {object} optional to request a registration in the nationchain network
|
||||
* if newtown exist then it send a request to update itself else it just refresh from existing town.
|
||||
* Check public nationchains are up to date from the existing list of towns
|
||||
* Each object to sync have a /idx/conf.json with key lastupdate = timestamp last update
|
||||
* tribes is not synchonized and contain private information
|
||||
* A town is a network node of the nationchains and allow to synchronize new
|
||||
* if not already exist Add a requested town into conf.towns.push({ "townId": "wall", "nationId": "ants", "dns": "wall-ants.ndda.fr" })
|
||||
*/
|
||||
const res= {status:400};
|
||||
const ref2update={}
|
||||
glob.sync(`${__dirapi}nationchains/**/idx/conf.json`).forEach(f=>{
|
||||
const ref=fs.readJsonSync(f)
|
||||
ref2update[path.basename(ref.schema,'.json')]=ref.lastupdate;
|
||||
})
|
||||
console.log(ref2update)
|
||||
// Get list of town to check n of them have fresh update
|
||||
const knowntowns =fs.readJsonSync(`${__dirapi}nationchains/towns/idx/towns_townId_all.json`);
|
||||
let promiselistblock=[]
|
||||
let towidlist=[]
|
||||
Object.keys(knowntowns).forEach(townid=>{
|
||||
// identify the town with the highest block to update town
|
||||
promiselistblock.push(axios.get(`https://${knowntowns[townid].url}/blocks/idx/conf.json`));
|
||||
townidlistblock.push(townid)
|
||||
});
|
||||
let selectedtown=""
|
||||
let blocnum=0
|
||||
await Promise.all(promiselistblock)
|
||||
.then(rep=>{
|
||||
for (let pos=0;pos<townidlist.length;pos++){
|
||||
if (rep[pos].blocnum > blocnum) {
|
||||
selectedtown=townidlist[pos]
|
||||
blocnum=rep[pos].blocnum
|
||||
};
|
||||
|
||||
Nations.updateobjectsfromfreshesttown = (dnstownlist, objectidx) => {
|
||||
/**
|
||||
* Get lasttime update per apxtrib object then choose the latest source and update local town
|
||||
* if an item exist localy and does not from the town requested
|
||||
* @Param {array} dnstownlist list of dns to get latest data
|
||||
* @Param {object} objectidx objectnme:idxfile {agans:"alias_all.json",...}
|
||||
* @return create/update nationchains/pagans town nation
|
||||
*/
|
||||
const localversion = {};
|
||||
const objlist = Object.keys(objectidx);
|
||||
objlist.forEach((o) => {
|
||||
let objconf = {
|
||||
name: o,
|
||||
schema: `nationchains/schema/${o}.jsons`,
|
||||
lastupdate: -1,
|
||||
};
|
||||
if (fs.existsSync(`${conf.dirapi}/nationchains/${o}/conf.json`)) {
|
||||
objconf = fs.readJsonSync(`${conf.dirapi}/nationchains/${o}/conf.json`);
|
||||
} else {
|
||||
fs.outputJsonSync(`${conf.dirapi}/nationchains/${o}/conf.json`, objconf);
|
||||
}
|
||||
localversion[o] = [conf.dns[0], objconf.lastupdate];
|
||||
});
|
||||
//console.log(localversion);
|
||||
for (let t = 0; t < dnstownlist.length; t++) {
|
||||
let promiseconf = [];
|
||||
let objecttotest = [];
|
||||
objlist.forEach((o) => {
|
||||
//console.log(`https://${dnstownlist[t].dns}/nationchains/${o}/conf.json`);
|
||||
objecttotest.push(o);
|
||||
promiseconf.push(
|
||||
axios.get(`https://${dnstownlist[t].dns}/nationchains/${o}/conf.json`)
|
||||
);
|
||||
});
|
||||
Promise.all(promiseconf)
|
||||
.then((reps) => {
|
||||
let promiseidx = [];
|
||||
let objecttoupdate = [];
|
||||
let objlastupdate = [];
|
||||
for (let i = 0; i < objecttotest.length; i++) {
|
||||
if (
|
||||
parseInt(reps[i].data.lastupdate) >
|
||||
parseInt(localversion[reps[i].data.name][1])
|
||||
) {
|
||||
// add promise to get data
|
||||
/*console.log(
|
||||
`https://${dnstownlist[t].dns}/nationchains/${
|
||||
reps[i].data.name
|
||||
}/idx/${objectidx[reps[i].data.name]}`
|
||||
);*/
|
||||
objecttoupdate.push(objecttotest[i]);
|
||||
objlastupdate.push(reps[i].data.lastupdate);
|
||||
promiseidx.push(
|
||||
axios.get(
|
||||
`https://${dnstownlist[t].dns}/nationchains/${
|
||||
reps[i].data.name
|
||||
}/idx/${objectidx[reps[i].data.name]}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Promise.all(promiseidx)
|
||||
.then((rets) => {
|
||||
for (let j = 0; j < objecttoupdate.length; j++) {
|
||||
Odmdb.updatefromidxall(
|
||||
objecttoupdate[j],
|
||||
objectidx[objecttoupdate[j]],
|
||||
rets[j].data,
|
||||
objlastupdate[j]
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("ERR get idx data");
|
||||
console.log(err);
|
||||
});
|
||||
})
|
||||
.catch(err=>{
|
||||
console.log(err)
|
||||
})
|
||||
let promiselistref=[]
|
||||
Object.keys(ref2update).forEach(ob=>{
|
||||
promiselistref.push(axios.get(`${knowntowns[selectedtown].url}/${obj}/idx/conf.json`));
|
||||
})
|
||||
await Promise.all(promiselistref)
|
||||
.then(rep=>{
|
||||
for (let pos=0;pos<townidlist.length;pos++){
|
||||
//si axios lastupdate > local lastupate => recupe _all et regenere tous les objets par ecrasement
|
||||
}
|
||||
})
|
||||
.catch(err=>{
|
||||
console.log(err)
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
Nations.update=(nationsource)=>{
|
||||
/**
|
||||
* Update object nation with last update
|
||||
*/
|
||||
}
|
||||
|
||||
Nations.synchronize = () => {
|
||||
.catch((err) => {
|
||||
console.log("ERR get conf lastupdate");
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
Nations.synchronizeold = () => {
|
||||
/*
|
||||
Run process to communicate with a list of towns to update network and transaction
|
||||
*/
|
||||
@ -189,8 +217,8 @@ Nations.create = (conf) => {
|
||||
@return
|
||||
*/
|
||||
const res = {};
|
||||
if (conf.object=="towns"){
|
||||
Odmdb.create("nationchains/socialworld/objects","towns",conf);
|
||||
if (conf.object == "towns") {
|
||||
Odmdb.create("nationchains/socialworld/objects", "towns", conf);
|
||||
}
|
||||
const nations = fs.readJsonSync(
|
||||
"./nationchains/nations/idx/nationId_all.json"
|
||||
@ -200,7 +228,7 @@ Nations.create = (conf) => {
|
||||
res.info = `your nationId ${conf.nationId} does not exist you have to choose an existing one`;
|
||||
return res;
|
||||
}
|
||||
const towns = fs.readJsonSync( "./nationchains/towns/idx/townId_all.json")
|
||||
const towns = fs.readJsonSync("./nationchains/towns/idx/townId_all.json");
|
||||
if (towns[conf.nationId].includes(conf.townId)) {
|
||||
res.status = 409;
|
||||
res.info = `This conf.townId already exist you have to find a unique town name`;
|
||||
@ -210,18 +238,20 @@ Nations.create = (conf) => {
|
||||
uuid: conf.townId,
|
||||
nationid: conf.nationId,
|
||||
url: `${conf.townId}.${conf.nationId}.${conf.dns}`,
|
||||
status: (conf.dns=="unchain")? "unchain" : "tochain",
|
||||
status: conf.dns == "unchain" ? "unchain" : "tochain",
|
||||
};
|
||||
const metatown=fs.readJsonSync('./nationchains/socialworld/metaobject/towns.json');
|
||||
Odmdb.add(objectpath, towns, metatown,towndata)
|
||||
|
||||
const metatown = fs.readJsonSync(
|
||||
"./nationchains/socialworld/metaobject/towns.json"
|
||||
);
|
||||
Odmdb.add(objectpath, towns, metatown, towndata);
|
||||
|
||||
fs.outputJsonSync(
|
||||
`./nationchains/socialworld/objects/towns/${townId}.json`,
|
||||
towndata
|
||||
);
|
||||
res.status=200
|
||||
res.info=`${townId} create for ${nationId} nation`;
|
||||
return res
|
||||
res.status = 200;
|
||||
res.info = `${townId} create for ${nationId} nation`;
|
||||
return res;
|
||||
};
|
||||
|
||||
module.exports = Nations;
|
||||
|
@ -1,8 +1,8 @@
|
||||
const glob = require("glob");
|
||||
const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
const axios = require('axios');
|
||||
const conf=require(`${process.env.dirtown}/conf.json`)
|
||||
const dayjs = require("dayjs");
|
||||
const conf = require(`${process.env.dirtown}/conf.json`);
|
||||
const Checkjson = require(`./Checkjson.js`);
|
||||
|
||||
/* This manage Objects for indexing and check and act to CRUD
|
||||
@ -20,11 +20,12 @@ Input: metaobject => data mapper of Key: Value
|
||||
objname + action index => update /searcindex of objects concern
|
||||
|
||||
*/
|
||||
Odmdb.setObject=(schemaPath, objectPath, objectName, schema, lgjson, lg)=>{
|
||||
/**
|
||||
*
|
||||
|
||||
Odmdb.setObject = (schemaPath, objectPath, objectName, schema, lgjson, lg) => {
|
||||
/**
|
||||
*
|
||||
* @schemapath {string} path to create or replace a schema ${schemaPath}/schema/
|
||||
* @objectPath {string} path where object are store
|
||||
* @objectPath {string} path where object are store
|
||||
* @objectName {string} name of the object
|
||||
* @schema {object} the json schema for this object
|
||||
* @lgjson {object} the json file for a specific language
|
||||
@ -37,26 +38,51 @@ Odmdb.setObject=(schemaPath, objectPath, objectName, schema, lgjson, lg)=>{
|
||||
* objectPath/objectName/idx/confjson ={"schema":"relativpathfile or http"}
|
||||
* /uniqueid.json defining schema
|
||||
*
|
||||
*/
|
||||
if (!fs.existsSync(schemaPath)){
|
||||
return {status:404, ref:"Odmdb", info:"pathnamedoesnotexist", moreinfo:{fullpath:schemaPath}}
|
||||
*/
|
||||
if (!fs.existsSync(schemaPath)) {
|
||||
return {
|
||||
status: 404,
|
||||
ref: "Odmdb",
|
||||
info: "pathnamedoesnotexist",
|
||||
moreinfo: { fullpath: schemaPath },
|
||||
};
|
||||
}
|
||||
if (!fs.existsSync(objectPath)){
|
||||
return {status:404, ref:"Odmdb", info:"pathnamedoesnotexist",moreinfo:{fullpath:objectPath}}
|
||||
if (!fs.existsSync(objectPath)) {
|
||||
return {
|
||||
status: 404,
|
||||
ref: "Odmdb",
|
||||
info: "pathnamedoesnotexist",
|
||||
moreinfo: { fullpath: objectPath },
|
||||
};
|
||||
}
|
||||
// store schema file if not empty undefined or {}
|
||||
if (schema && !(Object.keys(schema).length === 0 && schema.constructor === Object)){
|
||||
fs.outputJSONSync(`${schemaPath}/schema/${objectName}.json`,schema, {spaces:2})
|
||||
if (
|
||||
schema &&
|
||||
!(Object.keys(schema).length === 0 && schema.constructor === Object)
|
||||
) {
|
||||
fs.outputJSONSync(`${schemaPath}/schema/${objectName}.json`, schema, {
|
||||
spaces: 2,
|
||||
});
|
||||
}
|
||||
if (lgjson && lg && !(Object.keys(lgjson).length === 0 && lgjson.constructor === Object)){
|
||||
fs.outputJSONSync(`${schemaPath}/lg/${objectName}_${lg}.json`,lgjson, {spaces:2})
|
||||
if (
|
||||
lgjson &&
|
||||
lg &&
|
||||
!(Object.keys(lgjson).length === 0 && lgjson.constructor === Object)
|
||||
) {
|
||||
fs.outputJSONSync(`${schemaPath}/lg/${objectName}_${lg}.json`, lgjson, {
|
||||
spaces: 2,
|
||||
});
|
||||
}
|
||||
//create environnement object with the new schema config
|
||||
if (!fs.existsSync(`${objectPath}/${objectName}`)){
|
||||
fs.outputJsonSync(`${objectPath}/${objectName}/idx/confjson`,{schema:`${schemaPath}/schema/${objectName}.json`},{spaces:2})
|
||||
if (!fs.existsSync(`${objectPath}/${objectName}`)) {
|
||||
fs.outputJsonSync(
|
||||
`${objectPath}/${objectName}/idx/confjson`,
|
||||
{ schema: `${schemaPath}/schema/${objectName}.json` },
|
||||
{ spaces: 2 }
|
||||
);
|
||||
}
|
||||
return {status:200}
|
||||
}
|
||||
return { status: 200 };
|
||||
};
|
||||
|
||||
Odmdb.schema = (schemaPath, objectName, withschemacheck) => {
|
||||
// Return schema if exist and objectpath contain objectName { status:200;data:schema}
|
||||
@ -76,7 +102,7 @@ Odmdb.schema = (schemaPath, objectName, withschemacheck) => {
|
||||
const schema = fs.readJsonSync(`${schemaPath}/schema/${objectName}.json`);
|
||||
// check schema apx validity specificities primary unique ans searchindex
|
||||
if (withschemacheck) {
|
||||
if (!schema.apxprimarykey) {
|
||||
if (!schema.apxprimarykey) {
|
||||
// code 422: unprocessable Content
|
||||
return {
|
||||
status: 422,
|
||||
@ -137,12 +163,14 @@ Odmdb.Checkjson = (objectPath, objectName, data, withschemacheck) => {
|
||||
*/
|
||||
const res = { status: 200 };
|
||||
//get schema link of object
|
||||
const schemaPath = fs.readJsonSync(`${objectPath}/${objectName}/idx/confjson`)['schema']
|
||||
if (schemaPath.substring(0,4)=="http"){
|
||||
// lance requete http pour recuperer le schema
|
||||
}else{
|
||||
schema=="!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||
}
|
||||
const schemaPath = fs.readJsonSync(
|
||||
`${objectPath}/${objectName}/idx/confjson`
|
||||
)["schema"];
|
||||
if (schemaPath.substring(0, 4) == "http") {
|
||||
// lance requete http pour recuperer le schema
|
||||
} else {
|
||||
schema == "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
|
||||
}
|
||||
// check schema validity
|
||||
const schema = Odmdb.schema(objectPath, objectName, withschemacheck);
|
||||
if (schema.status != 200) return schema;
|
||||
@ -260,4 +288,74 @@ console.log(
|
||||
{ nationId: "123", status: "unchain" }
|
||||
)
|
||||
);*/
|
||||
|
||||
Odmdb.updatefromidxall = (objectname, idxname, data, lastupdate) => {
|
||||
/**
|
||||
* Update all itm of objectname from index idx/idxname with data
|
||||
* if itm exist in local and not in data then /ojectname/conf.json.lastupdate = now
|
||||
* not then /ojectname/conf.json.lastupdate = lastupdate
|
||||
* this way mean next time server A want to refresh from B its lastupdate < than on A
|
||||
*/
|
||||
let conflastupdate = 0;
|
||||
let localidx = {};
|
||||
if (
|
||||
fs.existsSync(`${conf.dirapi}/nationchains/${objectname}/idx/${idxname}`)
|
||||
) {
|
||||
localidx = fs.readJsonSync(
|
||||
`${conf.dirapi}/nationchains/${objectname}/idx/${idxname}`
|
||||
);
|
||||
}
|
||||
Object.keys(data).forEach((id) => {
|
||||
if (localidx[id]) {
|
||||
if (
|
||||
localidx[id].dt_update &&
|
||||
data[id].dt_update &&
|
||||
localidx[id].dt_update > data[id].dt_update
|
||||
) {
|
||||
// means local information is fresher than the one in data for replacement
|
||||
conflastupdate = dayjs();
|
||||
} else {
|
||||
// replace itm with data
|
||||
localidx[id] = data[id];
|
||||
fs.outputJsonSync(
|
||||
`${conf.dirapi}/nationchains/${objectname}/itm/${id}.json`,
|
||||
data[id]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// add itm
|
||||
localidx[id] = data[id];
|
||||
fs.outputJsonSync(
|
||||
`${conf.dirapi}/nationchains/${objectname}/itm/${id}.json`,
|
||||
data[id]
|
||||
);
|
||||
}
|
||||
});
|
||||
//check if it miss id in fresher update means conf.lastupdate will be now to indicate
|
||||
Object.keys(localidx).forEach((id) => {
|
||||
if (!data[id]) {
|
||||
conflastupdate = dayjs();
|
||||
}
|
||||
});
|
||||
// update the object files
|
||||
if (conflastupdate == 0) conflastupdate = lastupdate;
|
||||
fs.outputJSONSync(
|
||||
`${conf.dirapi}/nationchains/${objectname}/idx/${idxname}`,
|
||||
localidx
|
||||
);
|
||||
const objconf = fs.readJsonSync(
|
||||
`${conf.dirapi}/nationchains/${objectname}/conf.json`
|
||||
);
|
||||
objconf.lastupdate = conflastupdate;
|
||||
fs.outputJsonSync(
|
||||
`${conf.dirapi}/nationchains/${objectname}/conf.json`,
|
||||
objconf
|
||||
);
|
||||
return {
|
||||
status: 200,
|
||||
ref: "Odmdb.js",
|
||||
info: "Successfullupdate",
|
||||
data: { objectname, idxname, lastupdate },
|
||||
};
|
||||
};
|
||||
module.exports = Odmdb;
|
||||
|
@ -9,7 +9,7 @@ const openpgp = require("openpgp");
|
||||
conf = require("../../nationchains/tribes/conf.json");
|
||||
}*/
|
||||
const conf = require(`${process.env.dirtown}/conf.json`);
|
||||
console.log(conf);
|
||||
|
||||
/**
|
||||
* Pagan Management numeric Identity and Person (Person = Pagan Id + tribe)
|
||||
*
|
||||
@ -58,7 +58,11 @@ Pagans.personupdate = (alias, tribe, persondata) => {
|
||||
dt_create: dayjs(),
|
||||
accessrights: { profil: "user" },
|
||||
};
|
||||
if (fs.existsSync(`${process.env.dirtown}/tribes/${tribe}/person/itm/${alias}.json`)) {
|
||||
if (
|
||||
fs.existsSync(
|
||||
`${process.env.dirtown}/tribes/${tribe}/person/itm/${alias}.json`
|
||||
)
|
||||
) {
|
||||
person = fs.readJsonSync(
|
||||
`${process.env.dirtown}/tribes/${tribe}/person/itm/${alias}.json`
|
||||
);
|
||||
|
@ -1,12 +1,10 @@
|
||||
const { argv } = require('process');
|
||||
const { argv } = require("process");
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const dnsSync = require("dns-sync");
|
||||
const mustache = require("mustache");
|
||||
const readlineSync = require("readline-sync");
|
||||
|
||||
const Wwws = require("../models/Wwws.js");
|
||||
const Nations = require("../models/Nations.js");
|
||||
/**
|
||||
* This Setup is run at the first installation
|
||||
* This is not an exportable module
|
||||
@ -20,33 +18,70 @@ const Setup = {};
|
||||
|
||||
Setup.check = (param) => {
|
||||
//run a nationchains sync nd check town does not already exist
|
||||
["nationId","townId","url"].forEach(k=>{
|
||||
if (!param[k]){
|
||||
console.log(`Please provide a ${k} in your param`)
|
||||
["nationId", "townId", "dns", "chain"].forEach((k) => {
|
||||
if (!param[k]) {
|
||||
console.log(`Please provide a ${k} in your param`);
|
||||
process.exit();
|
||||
}
|
||||
})
|
||||
const initconf = fs.readJsonSync('./adminapi/www/adminapx/tpldata/initconf.json')
|
||||
let nationupdated=false;
|
||||
for (let t=0; t<initconf.towns;t++){
|
||||
const synchro = Nations.synchronize(initconf.towns[t].url)
|
||||
if (synchro.status==200){
|
||||
nationupdated=true;
|
||||
break;}
|
||||
});
|
||||
|
||||
const localconf = {
|
||||
nationId: param.nationId,
|
||||
townId: param.townId,
|
||||
url:param.dns,
|
||||
comment: "Generate by setup.js with minimum of information",
|
||||
};
|
||||
if (fs.existsSync(`../../adminapi/www/adminapx/conf/setup_xx.json`)) {
|
||||
const setupxx=readJsonSYnc(`../../adminapi/www/adminapx/conf/setup_xx.json`)
|
||||
if (setupxx.nationId==param.nationId && setupxx.townId=param.townId) {
|
||||
// This is a re-install
|
||||
}else{
|
||||
console.log(
|
||||
"A conf file exist in adminapi/www/adminapx/conf/setup_xx.json remove it an run again yarn setup ..."
|
||||
);
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
if (!nationupdated){
|
||||
Console.log('Please check your internet access other towns cannot be reach to synchronize your town')
|
||||
fs.outputJsonSync(
|
||||
`../../adminapi/www/adminapx/conf/setup_xx.json`,
|
||||
localconf
|
||||
);
|
||||
const initconf = fs.readJsonSync(
|
||||
"./adminapi/www/adminapx/tpldata/initconf.json"
|
||||
);
|
||||
let nationupdated = false;
|
||||
const Wwws = require("../models/Wwws.js");
|
||||
const Nations = require("../models/Nations.js");
|
||||
for (let t = 0; t < initconf.towns; t++) {
|
||||
const synchro = Nations.synchronize(initconf.towns[t].dns);
|
||||
if (synchro.status == 200) {
|
||||
nationupdated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!nationupdated) {
|
||||
Console.log(
|
||||
"Please check your internet access other towns cannot be reach to synchronize your town"
|
||||
);
|
||||
process.exit();
|
||||
}
|
||||
// check nationId exist and townId not
|
||||
if (!Object.keys(fs.readJsonSync('../../nationchains/nations/idx/nationId_all.json')).includes(param.nationId) {
|
||||
console.log(`Sorry your nationId: ${param.nationId} does not exist`);
|
||||
process.exit();
|
||||
}
|
||||
if (Object.keys(fs.readJsonSync('../../nationchains/towns/idx/townId_all.json')).includes(param.townId) {
|
||||
console.log(`Sorry your townId: ${param.townId} already exist, please choose another one`);
|
||||
const idxnationId = fs.readJsonSync(
|
||||
`../../nationchains/nations/idx/nationId_all.json`
|
||||
);
|
||||
if (!Object.keys(idxnationId).includes(param.nationId)) {
|
||||
console.log(`Sorry your nationId: ${param.nationId} does not exist`);
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
const idxtownId = fs.readJsonSync(
|
||||
"../../nationchains/towns/idx/townId_all.json"
|
||||
);
|
||||
if (Object.keys(idxtownId).includes(param.townId)) {
|
||||
console.log(
|
||||
`Sorry your townId: ${param.townId} already exist, please choose another one`
|
||||
);
|
||||
process.exit();
|
||||
}
|
||||
if ("testinternet" != "testinternet") {
|
||||
console.log(
|
||||
"\x1b[31m Check your internet access, to setup this town we need to update the Nations. It seems we cannot do it"
|
||||
@ -76,20 +111,20 @@ Setup.init = async (param) => {
|
||||
* Then to send new version we fix a master production
|
||||
*
|
||||
*/
|
||||
const initconf = fs.readJsonSync('./adminapi/www/adminapx/tpldata/initconf.json')
|
||||
const initconf = fs.readJsonSync(
|
||||
"./adminapi/www/adminapx/tpldata/initconf.json"
|
||||
);
|
||||
|
||||
initconf.sudoerUser = process.env.USER;
|
||||
initconf.dirapi = path.resolve(`${__dirname}/../../`);
|
||||
initconf.dirtown = path.resolve(`${__dirname}/../../../${param.townId}-${param.nationId}/`)
|
||||
initconf.dirtown = path.resolve(
|
||||
`${__dirname}/../../../${param.townId}-${param.nationId}/`
|
||||
);
|
||||
// To allow to serve the nation website until the end
|
||||
initconf.nginx.include.push(
|
||||
`${initconf.dirapi}/adminapi/www/nginx_*.conf`
|
||||
);
|
||||
initconf.nginx.include.push(`${initconf.dirapi}/adminapi/www/nginx_*.conf`);
|
||||
// To allow to serve tribes web site
|
||||
initconf.nginx.include.push(
|
||||
`${initconf.dirtown}/tribes/*/www/nginx_*.conf`
|
||||
);
|
||||
initconf.dns.push(param.url)
|
||||
initconf.nginx.include.push(`${initconf.dirtown}/tribes/*/www/nginx_*.conf`);
|
||||
initconf.dns.push(param.dns);
|
||||
initconf.nginx.logs = `${dirtown}/logs/nginx/adminapx`;
|
||||
initconf.nginx.website = "adminapx";
|
||||
initconf.nginx.fswww = `${dirapi}/adminapi/www`;
|
||||
@ -121,32 +156,38 @@ Setup.init = async (param) => {
|
||||
);
|
||||
if (!fs.existsSync(initconf.nginx.logs)) fs.mkdirSync(initconf.nginx.logs);
|
||||
const { exec } = require("child_process");
|
||||
exec(.restart, (error, stdout, stderr) => {
|
||||
exec(initconf.restart, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.log("\x1b[42m", error, stdout, stderr, "x1b[0m");
|
||||
//@todo supprimer la derniere config et relancer
|
||||
console.log("\x1b[42m", error, stdout, stderr, "x1b[0m");
|
||||
//@todo supprimer la derniere config et relancer
|
||||
} else {
|
||||
console.log(
|
||||
`\x1b[42m###########################################################################################\x1b[0m\n\x1b[42mWellcome into apxtrib, you can now 'yarn dev' for dev or 'yarn startpm2' for prod or \n'yarn unittest' for testing purpose. Access to your town here \x1b[0m\x1b[32mhttp://${param.url}\x1b[0m \x1b[42m \nto finish your town setup. Don't forget to set your localhost /etc/hosts by adding 127.0.0.1 adminapx or {LAN IP} adminapx . Check README's project to learn more. \x1b[0m\n\x1b[42m###########################################################################################\x1b[0m`
|
||||
);
|
||||
console.log(
|
||||
`\x1b[42m###########################################################################################\x1b[0m\n\x1b[42mWellcome into apxtrib, you can now 'yarn dev' for dev or 'yarn startpm2' for prod or \n'yarn unittest' for testing purpose. Access to your town here \x1b[0m\x1b[32mhttp://${param.dns}\x1b[0m \x1b[42m \nto finish your town setup. Don't forget to set your localhost /etc/hosts by adding 127.0.0.1 adminapx or {LAN IP} adminapx . Check README's project to learn more. \x1b[0m\n\x1b[42m###########################################################################################\x1b[0m`
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (argv.length!=5) {
|
||||
console.log("Check you parameter you need to type yarn setup nationId:ants townid:mytown url:mytown-ants ")
|
||||
process.exit()
|
||||
if (argv.length != 6) {
|
||||
console.log(
|
||||
"Check you parameter you need to type yarn setup nationId:ants townid:mytown dns:mytown-ants "
|
||||
);
|
||||
process.exit();
|
||||
}
|
||||
const param={};
|
||||
argv.slice(2).forEach(arg=>{
|
||||
const kv=arg.split(':')
|
||||
if (kv.length==2){
|
||||
param[kv[0]]=kv[1]
|
||||
}else{
|
||||
console.log('Check your args that have to be yarn main key1:val1 keyn:valn');
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
if (Setup.check(param)) Setup.init(param);
|
||||
const param = {};
|
||||
argv.slice(2).forEach((arg) => {
|
||||
const kv = arg.split(":");
|
||||
if (kv.length == 2) {
|
||||
param[kv[0]] = kv[1];
|
||||
} else {
|
||||
console.log(
|
||||
"Check your args that have to be yarn main key1:val1 keyn:valn"
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
console.log(param);
|
||||
//if (Setup.check(param)) Setup.init(param);
|
||||
|
||||
// After testing remove all stuff after this line
|
||||
|
||||
@ -251,7 +292,7 @@ Setup.initold = async () => {
|
||||
/* townconf.town = {
|
||||
townId: townconf.townId,
|
||||
nationId: townconf.nationId,
|
||||
url: `http://${townconf.dns[0]}`,
|
||||
dns: `http://${townconf.dns[0]}`,
|
||||
IP: townconf.IP,
|
||||
mayorid: townconf.mayorId,
|
||||
status: "unchain",
|
||||
@ -469,4 +510,3 @@ Setup.druidid = (townSetup) => {
|
||||
console.log("Issue ", createclient);
|
||||
}
|
||||
};
|
||||
|
||||
|
259
apxtrib.js
259
apxtrib.js
@ -1,4 +1,6 @@
|
||||
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");
|
||||
@ -10,56 +12,209 @@ 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
|
||||
*/
|
||||
// Global data : add here globale variable that take care between RAM space anf fs access
|
||||
// to make absolute path with `${__dirapi}relativepath`
|
||||
//global.__dirapi = __dirname + "/";
|
||||
// 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 ...
|
||||
*/
|
||||
|
||||
// check setup
|
||||
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`);
|
||||
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();
|
||||
}
|
||||
|
||||
if (!fs.existsSync(`${__dirname}/adminapi/www/adminapx/static/tpldata/setup_xx.json`)) {
|
||||
// Need to generate a setup
|
||||
console.log(
|
||||
`\x1b[42m############################################################################################\x1b[0m\n\x1b[42mWellcome into apxtrib, you must run 'yarn setup nationId townId dns' to conf your town. \x1b[0m \n\x1b[42m where nationId have to exist (example: ants) townId is new and dns have to be a domaine set to listen on port 80 443 on this server (example wall-ants.ndda.fr redirect to 213.32.65.213 or usbfarm-ants redirect to 127.0.0.1). Check README's project to learn more.\x1b[0m\n\x1b[42m############################################################################################\x1b[0m`
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
const infotown = fs.readJsonSync(`${__dirname}/adminapi/www/adminapx/static/tpldata/setup_xx.json`)
|
||||
if (!process.env.dirtown) process.env.dirtown=path.resolve(`${__dirname}/../${infotown.townId}-${infotown.nationId}`);
|
||||
|
||||
const conf = require(`${process.env.dirtown}/conf.json`);
|
||||
// To make public this conf, careffull this localconf will be public, this the only difference between all apxtrib node
|
||||
/*
|
||||
const localconf = {
|
||||
nationId: conf.nationId,
|
||||
townId: conf.townId,
|
||||
tribeId: conf.tribeId,
|
||||
comment: "Generate by apxtrib.js with minimum of information",
|
||||
};
|
||||
fs.outputJsonSync(
|
||||
`${__dirapi}nationchains/www/adminapx/static/tpldata/setup_en.json`,
|
||||
localconf
|
||||
);*/
|
||||
|
||||
// Run main express process
|
||||
|
||||
// Each tribe has a context (domain, plugins route, website ) are all describe into idx tribeId_all.json
|
||||
// {"tribename":{"tribeId":"tribename","dns":[array of domain],"status":"unchain","nationId":"ants","townId":"usbfarm"}}
|
||||
|
||||
// dataclient .tribeids [] .DOMs [] .routes (plugins {url:name route:path}) .appname {tribeid:[website]}
|
||||
//const dataclient = require( './api/models/Tribes' ).init();
|
||||
const tribelist = fs.readJsonSync(
|
||||
`${process.env.dirtown}/tribes/idx/tribeId_all.json`
|
||||
// From git setup-xx is set to nationId:ant townId:farmdev (keyword for dev)
|
||||
const infotown = fs.readJsonSync(
|
||||
`${__dirname}/adminapi/www/adminapx/conf/setup_xx.json`
|
||||
);
|
||||
|
||||
if (
|
||||
!fs.existsSync(
|
||||
path.resolve(
|
||||
`${__dirname}/../${infotown.townId}-${infotown.nationId}/conf.json`
|
||||
)
|
||||
) ||
|
||||
!fs.existsSync(`${__dirname}/adminapi/www/nginx_adminapx.conf`)
|
||||
) {
|
||||
// Run setup with information setup_xx.json
|
||||
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) => {
|
||||
@ -125,27 +280,33 @@ const corsOptions = {
|
||||
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'
|
||||
dotfiles: 'allow'
|
||||
} ) );
|
||||
*/
|
||||
// Routers add any routes from /routes and /plugins
|
||||
console.log("Routes available on this apxtrib instance");
|
||||
console.log(routes);
|
||||
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) {
|
||||
console.log(
|
||||
`\x1b[31m!!! WARNING issue with route ${r.route} from ${r.url} check err if route is key then solve err, if not just be aware that this route won't work on your server. If you are not the maintainer and no turn around please contact the email maintainer.\x1b[0m`
|
||||
);
|
||||
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)
|
||||
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",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"scripts": {
|
||||
"stoppm2": "pm2 stop apxtrib.js",
|
||||
"startpm2": "pm2 start apxtrib.js --log-date-format 'DD-MM HH:mm:ss.SSS'",
|
||||
"deletepm2":"pm2 delete apxtrib",
|
||||
"restartpm2": "pm2 restart apxtrib.js --log-date-format 'DD-MM HH:mm:ss.SSS'",
|
||||
"startblockchain": "pm2 start api/models/Blockchains.js --log-date-format 'DD-MM HH:mm:ss:SSS'",
|
||||
"logpm2": "pm2 logs apxtrib.js --lines 200",
|
||||
|
Loading…
Reference in New Issue
Block a user