├── .gitignore ├── bundle.sh ├── changelog.txt ├── config ├── controllers ├── api.js └── default.js ├── definitions ├── auth.js ├── monitoring.js ├── prototypes.js ├── superadmin.js └── tms.js ├── dockerfile ├── dockerfile.alpine ├── ffdhe2048.pem ├── index.js ├── install-alpine.sh ├── install-centos.sh ├── install.sh ├── jsonschemas └── apps_monitor.json ├── license.txt ├── nginx.conf ├── package.json ├── private ├── backup.sh ├── backuplogs.sh ├── ftp.sh ├── index.js ├── mkdir.sh ├── nginx.conf ├── npminstall.sh ├── superadmin.csr ├── superadmin.key └── update-total.sh ├── public ├── css │ ├── default.css │ └── ui.css ├── favicon.ico ├── forms │ ├── alarm.html │ ├── analyzator.html │ ├── app.html │ ├── filebrowser.html │ ├── intro.html │ ├── profile.html │ ├── service.html │ ├── summary.html │ ├── terminal.html │ ├── upload.html │ └── user.html ├── icon.png ├── js │ ├── helpers.js │ ├── syntax.js │ └── ui.js ├── parts │ ├── alarms.html │ ├── apps.html │ ├── settings.html │ └── users.html └── robots.txt ├── readme.md ├── reconfigure.sh ├── resources └── default.resource ├── run-docker-alpine.sh ├── run-docker.sh ├── run.sh ├── schemas ├── account-login.js ├── account.js ├── alarms.js ├── apps-build.js ├── apps.js ├── filebrowser.js ├── notifications.js ├── operations.js ├── settings.js ├── templates.js └── users.js ├── selfsigned.sh ├── ssl.sh ├── superadmin.bundle ├── superadmin.conf ├── tasks ├── nginx.js └── ssl.js ├── update-centos.sh ├── update.sh ├── user.guid └── views ├── index.html ├── login.html └── mails └── summarization.html /.gitignore: -------------------------------------------------------------------------------- 1 | sftp-config.json 2 | superadmin.log 3 | databases/applications.json 4 | debug.pid 5 | tmp/ -------------------------------------------------------------------------------- /bundle.sh: -------------------------------------------------------------------------------- 1 | mkdir -p .bundle 2 | 3 | cd .bundle 4 | cp -a ../controllers/ controllers 5 | cp -a ../definitions/ definitions 6 | cp -a ../public/ public 7 | cp -a ../private/ private 8 | cp -a ../jsonschemas/ jsonschemas 9 | cp -a ../schemas/ schemas 10 | cp -a ../resources/ resources 11 | cp -a ../tasks/ tasks 12 | cp -a ../views/ views 13 | 14 | # cd .. 15 | total4 --bundle superadmin.bundle 16 | mv superadmin.bundle ../superadmin.bundle 17 | 18 | cd .. 19 | rm -rf .bundle 20 | echo "DONE" -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | ======= 9.0.0 2 | 3 | - updated source-code to Total.js 4 4 | - added support for Total.js 4 applications 5 | - fully redesigned 6 | - improved security 7 | - improved source-code 8 | - improved stability 9 | - improved generating of SSL 10 | - improved generating of NGINX configuration 11 | - improved performance 12 | - removed stats section 13 | - implementing websocket 14 | 15 | ======= 8.0.0 16 | 17 | - added stateless mode for ACME 18 | - added backup of `applications.json` 19 | - addef Filebrowser 20 | - added FTP 21 | - added Quick Access, just press F1 22 | - improved UI 23 | - improved code 24 | 25 | ======= 7.0.0 26 | 27 | - added auto-renew SSL certificates 28 | - added nice charts with monthly statistics 29 | - added option for enabling NGINX access logging 30 | - updated version of all client-side libraries (jQuery, jComponent and CodeMirror) 31 | - improved code 32 | - fixed analyzator for deprecated warnings 33 | - fixed a problem with determination of `nginx` binary 34 | - added support for Total.js components (group `superadmin`) 35 | 36 | ======= 6.0.0 37 | 38 | - add backend monitoring 39 | - add statistics 40 | 41 | ======= 5.0.0 42 | 43 | - add logs analyzator 44 | - add custom start scripts 45 | - add CouchDB consumption 46 | - add backuping of `applications.json` 47 | - add logging 48 | - app does not use `service nginx` command 49 | - add server last logins preview 50 | - add nginx config test preview 51 | - add SuperAdmin logs preview 52 | - add backuping of all applications logs in release mode 53 | - improve UI 54 | 55 | ======= 4.0.0 56 | 57 | - renew Let's encrypt certificates 58 | - watching SSL expiration 59 | - improve UI 60 | - update debug.js version 61 | - add memory limit -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | session_cookie (generate) : 4 2 | session_secret (generate) : 10 3 | unixsocket : true 4 | 5 | // Server configuration 6 | directory_nginx : /www/nginx/ 7 | directory_ssl : /www/ssl/ 8 | directory_acme : /www/acme/ 9 | directory_www : /www/www/ 10 | directory_console : /www/logs/ 11 | directory_dump : /www/ 12 | 13 | allow_tms : false -------------------------------------------------------------------------------- /controllers/api.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const Spawn = require('child_process').spawn; 3 | const Exec = require('child_process').exec; 4 | 5 | exports.install = function() { 6 | 7 | ROUTE('+API /apiv1/ -apps_query *Apps --> query'); 8 | ROUTE('+API /apiv1/ -apps_read/id *Apps --> read'); 9 | ROUTE('+API /apiv1/ -apps_restart/id *Apps --> restart'); 10 | ROUTE('+API /apiv1/ -apps_restart_all *Apps --> restart_all'); 11 | ROUTE('+API /apiv1/ +apps_save *Apps --> check port save (response)', [120000]); 12 | ROUTE('+API /apiv1/ -apps_remove/id *Apps --> remove'); 13 | ROUTE('+API /apiv1/ -apps_stop/id *Apps --> stop'); 14 | ROUTE('+API /apiv1/ -apps_stop_all *Apps --> stop_all'); 15 | ROUTE('+API /apiv1/ -apps_analyzator *Apps --> analyzator'); 16 | ROUTE('+API /apiv1/ -templates *Templates --> query'); 17 | ROUTE('+API /apiv1/ +templates_apply/id *Templates --> check stop backup remove unpack (response) restart', [50000]); 18 | ROUTE('+API /apiv1/ -fixpermissions *Operations --> fixpermissions', [50000]); 19 | ROUTE('+API /apiv1/ -settings_read *Settings --> read'); 20 | ROUTE('+API /apiv1/ +settings_save *Settings --> save'); 21 | ROUTE('+API /apiv1/ -updatetotal *Operations --> updatetotal'); 22 | ROUTE('+API /apiv1/ -filebrowser/id *FileBrowser --> query'); 23 | ROUTE('+API /apiv1/ -filebrowser_read/id *FileBrowser --> read'); 24 | ROUTE('+API /apiv1/ +filebrowser_save/id *FileBrowser --> save'); 25 | ROUTE('+API /apiv1/ -users_query *Users --> query'); 26 | ROUTE('+API /apiv1/ -users_read/id *Users --> read'); 27 | ROUTE('+API /apiv1/ -users_remove/id *Users --> remove'); 28 | ROUTE('+API /apiv1/ +users_insert *Users --> insert'); 29 | ROUTE('+API /apiv1/ +users_update/id *Users --> update'); 30 | ROUTE('+API /apiv1/ -account_read *Account --> read'); 31 | ROUTE('+API /apiv1/ +account_save *Account --> save'); 32 | ROUTE('+API /apiv1/ -alarms_query *Alarms --> query'); 33 | ROUTE('+API /apiv1/ +alarms_save *Alarms --> save'); 34 | ROUTE('+API /apiv1/ -alarms_remove/id *Alarms --> remove'); 35 | ROUTE('+API /apiv1/ -notifications_query *Notifications --> query'); 36 | ROUTE('+API /apiv1/ -notifications_clear *Notifications --> remove'); 37 | ROUTE('-API /apiv1/ +login *Account/Login --> exec'); 38 | ROUTE('+API /apiv1/ -logout *Account --> logout'); 39 | ROUTE('+GET /backup/ *Operations --> backup', [1200000]); 40 | ROUTE('GET /logs/{id}/ *Apps --> logs'); 41 | 42 | ROUTE('+API /apiv1/ -build_read/id *Apps/Build --> read'); 43 | ROUTE('+API /apiv1/ +build_save/id *Apps/Build --> save', 1024 * 10); 44 | 45 | // Custom defined actions 46 | ROUTE('+GET /download/{id}/', download, [120000]); 47 | ROUTE('+POST /api/upload/', upload, ['upload', 120000], 1024 * 100); // Max. 100 MB 48 | ROUTE('+POST /api/filebrowser/', upload_filebrowser, ['upload'], 1024 * 100); // Max. 100 MB 49 | 50 | ROUTE('+SOCKET /', socket, ['json']); 51 | }; 52 | 53 | function socket() { 54 | 55 | var self = this; 56 | 57 | MAIN.ws = self; 58 | self.autodestroy(() => MAIN.ws = null); 59 | 60 | self.on('open', function(client) { 61 | 62 | if (SuperAdmin.server) 63 | client.send(SuperAdmin.server); 64 | 65 | client.terminals = {}; 66 | 67 | var output = {}; 68 | 69 | output.TYPE = 'appsinfo'; 70 | 71 | for (var i = 0; i < APPLICATIONS.length; i++) { 72 | var item = APPLICATIONS[i]; 73 | if (!item.stopped && item.current) { 74 | item.current.id = item.id; 75 | item.current.is = true; 76 | item.current.analyzator = item.analyzatoroutput; 77 | output['app' + item.id] = item.current; 78 | } 79 | } 80 | 81 | client.send(output); 82 | }); 83 | 84 | self.on('close', function(client) { 85 | var keys = Object.keys(client.terminals); 86 | for (var i = 0; i < keys.length; i++) 87 | client.terminals[keys[i]].kill(9); 88 | }); 89 | 90 | self.on('message', function(client, msg) { 91 | 92 | var child; 93 | 94 | if (client.user.sa) { 95 | 96 | if (msg.TYPE === 'terminal_open') { 97 | 98 | var id = 'spawn' + UID(); 99 | 100 | child = Spawn('bash', [], { cwd: '/www/' }); 101 | client.send({ TYPE: 'terminal_open', id: id }); 102 | client.terminals[id] = child; 103 | 104 | child.stderr.on('data', function(chunk) { 105 | var msg = {}; 106 | msg.TYPE = 'terminal_data'; 107 | msg.iserror = true; 108 | msg.id = id; 109 | msg.body = chunk.toString('utf8').trim(); 110 | client.send(msg); 111 | }); 112 | 113 | child.stdout.on('data', function(chunk) { 114 | var msg = {}; 115 | msg.TYPE = 'terminal_data'; 116 | msg.id = id; 117 | msg.body = chunk.toString('utf8').trim(); 118 | client.send(msg); 119 | }); 120 | 121 | child.on('close', function() { 122 | delete client.terminals[id]; 123 | client.send({ TYPE: 'terminal_close', id: id }); 124 | }); 125 | 126 | } else if (msg.TYPE === 'terminal_send') { 127 | 128 | child = client.terminals[msg.id]; 129 | 130 | if (child) { 131 | client.send({ TYPE: 'terminal_data', id: msg.id, body: msg.body, send: true }); 132 | child.stdin.write(msg.body + '\n'); 133 | } 134 | 135 | } else if (msg.TYPE === 'terminal_close') { 136 | if (msg.id) { 137 | child = client.terminals[msg.id]; 138 | if (child) { 139 | child.kill(9); 140 | delete client.terminals[msg.id]; 141 | } 142 | } 143 | } else if (msg.TYPE === 'terminal_cancel') { 144 | child = client.terminals[msg.id]; 145 | child && child.kill(0); 146 | } 147 | } 148 | }); 149 | 150 | } 151 | 152 | function upload() { 153 | var self = this; 154 | var app = APPLICATIONS.findItem('id', self.query.id || ''); 155 | if (app) { 156 | SuperAdmin.logger('upload: {0}', self, app); 157 | var file = self.files[0]; 158 | var filename = Path.join(CONF.directory_www, app.url.superadmin_linker(app.path), app.id + '.zip'); 159 | file.move(filename, self.done(filename)); 160 | } else 161 | self.invalid('404'); 162 | } 163 | 164 | function download(id) { 165 | 166 | var self = this; 167 | var app = APPLICATIONS.findItem('id', id); 168 | if (!app) { 169 | self.invalid('404'); 170 | return; 171 | } 172 | 173 | var linker = app.url.superadmin_linker(); 174 | var directory = Path.join(CONF.directory_www, linker); 175 | var backup = Path.join(directory, linker + '_backup.zip'); 176 | 177 | SuperAdmin.logger('backup: {0}', self, app); 178 | 179 | Exec('zip -r {0} .??* * -x \\*.git\\* \\*.socket\\* \\*tmp\\* \\*node_modules\\*'.format(linker + '_backup.zip'), { cwd: directory }, function(err) { 180 | if (err) 181 | self.invalid().push(err); 182 | else 183 | self.file('~' + backup, U.getName(backup)); 184 | }); 185 | } 186 | 187 | function upload_filebrowser() { 188 | 189 | var self = this; 190 | 191 | if (!self.files.length) { 192 | self.invalid('error-file'); 193 | return; 194 | } 195 | 196 | $WORKFLOW('FileBrowser', 'upload', self.callback(), self); 197 | } -------------------------------------------------------------------------------- /controllers/default.js: -------------------------------------------------------------------------------- 1 | exports.install = function() { 2 | ROUTE('/*', 'index', ['authorize']); 3 | ROUTE('/*', 'login', ['unauthorize']); 4 | ROUTE('/logoff', redirect_logoff); 5 | }; 6 | 7 | function redirect_logoff() { 8 | var self = this; 9 | SuperAdmin.logger('logoff', self); 10 | self.cookie('__sa', '', '-1 day'); 11 | self.redirect('/'); 12 | } -------------------------------------------------------------------------------- /definitions/auth.js: -------------------------------------------------------------------------------- 1 | var opt = {}; 2 | 3 | opt.secret = CONF.session_secret; 4 | opt.cookie = CONF.session_cookie; 5 | opt.ddos = 10; 6 | opt.options = { samesite: 'lax', httponly: true }; 7 | 8 | opt.onread = function(meta, next) { 9 | 10 | // meta.sessionid {String} 11 | // meta.userid {String} 12 | // meta.ua {String} A user-agent 13 | 14 | NOSQL('sessions').read().id(meta.sessionid).where('userid', meta.userid).where('ua', meta.ua).callback(function(err, response) { 15 | if (response) { 16 | 17 | // Updates session 18 | NOSQL('sessions').modify({ '+logged': 1, dtlogged: NOW }).id(meta.sessionid); 19 | 20 | // Reads & Updates user profile 21 | var db = NOSQL('users'); 22 | db.mod({ '+logged': 1, dtlogged: NOW, isonline: true }).id(meta.userid).where('isdisabled', false); 23 | db.one().fields('id,name,sa').id(meta.userid).where('isdisabled', false).callback(next); 24 | 25 | } else 26 | next(); 27 | }); 28 | }; 29 | 30 | opt.onfree = function(meta) { 31 | if (meta.users.length) 32 | NOSQL('users').mod({ isonline: false }).in('id', meta.users); 33 | }; 34 | 35 | AUTH(opt); 36 | MAIN.session = opt; 37 | NOSQL('users').mod({ isonline: false }).where('isonline', true); -------------------------------------------------------------------------------- /definitions/monitoring.js: -------------------------------------------------------------------------------- 1 | function service_info() { 2 | $WORKFLOW('Apps', 'info', () => setTimeout(service_info, 60000)); 3 | } 4 | 5 | function service_analyzator() { 6 | $WORKFLOW('Apps', 'analyzator', () => setTimeout(service_analyzator, 120000)); 7 | } 8 | 9 | ON('settings', function() { 10 | service_info(); 11 | service_analyzator(); 12 | }); 13 | 14 | // Saves current apps state 15 | ON('service', function(counter) { 16 | 17 | counter % 2 === 0 && SuperAdmin.save(); 18 | 19 | if (!CONF.allowbackup || !CONF.intervalbackup) 20 | return; 21 | 22 | if ((counter / 60) % CONF.intervalbackup !== 0) 23 | return; 24 | 25 | SuperAdmin.logger('backup'); 26 | 27 | APPLICATIONS.wait(function(item, next) { 28 | if (item.backup) 29 | SuperAdmin.backupapp(item, next); 30 | else 31 | next(); 32 | }); 33 | }); 34 | 35 | ON('superadmin_app_restart', function(app) { 36 | 37 | if (!app) 38 | return; 39 | 40 | !app.stats && (app.stats = {}); 41 | 42 | if (app.stats.restarts) 43 | app.stats.restarts++; 44 | else 45 | app.stats.restarts = 1; 46 | }); -------------------------------------------------------------------------------- /definitions/prototypes.js: -------------------------------------------------------------------------------- 1 | const REG_PROTOCOL = /^(http|https):\/\//gi; 2 | 3 | // Tries to parse all domain names 4 | String.prototype.superadmin_domains = function() { 5 | 6 | var t = this + ''; 7 | var domain = t.split('.'); 8 | if (domain.length === 3) { 9 | 10 | // www.totaljs.com 11 | // test.totaljs.com 12 | if (domain[0] === 'www') { 13 | domain.shift(); 14 | return [t, domain.join('.')]; 15 | } 16 | 17 | } else if (domain.length === 2) { 18 | // totaljs.com 19 | return [t, 'www.' + t]; 20 | } 21 | 22 | return [t]; 23 | }; 24 | 25 | String.prototype.superadmin_url = function() { 26 | return this.replace(REG_PROTOCOL, '').replace(/\//g, ''); 27 | }; 28 | 29 | String.prototype.superadmin_nginxredirect = function() { 30 | return this.superadmin_redirect().replace(REG_PROTOCOL, ''); 31 | }; 32 | 33 | String.prototype.superadmin_redirect = function() { 34 | var end = this.substring(8); 35 | var index = end.indexOf('/'); 36 | if (index !== -1) 37 | end = end.substring(0, index); 38 | return this.substring(0, 8) + end; 39 | }; 40 | 41 | String.prototype.superadmin_linker = function(path) { 42 | var url = this.replace(REG_PROTOCOL, '').replace(/\//g, ''); 43 | var arr = url.split('.'); 44 | arr.reverse(); 45 | var tmp = arr[1]; 46 | arr[1] = arr[0]; 47 | arr[0] = tmp; 48 | return arr.join('-').replace('-', '_') + (path ? path.replace(/\//g, '--').replace(/--$/g, '') : ''); 49 | }; 50 | 51 | String.prototype.superadmin_clean = function() { 52 | var str = this; 53 | str = str.replace(/\/{3,}/g, '/'); 54 | var tmp = str.substring(8); 55 | var index = tmp.indexOf('/'); 56 | return index === -1 ? str : (str.substring(0, 8) + tmp.substring(0, index)); 57 | }; -------------------------------------------------------------------------------- /definitions/tms.js: -------------------------------------------------------------------------------- 1 | var notify = 'type:String,body:String,message:String,dtnotified:Date,dttms:Date'; 2 | var trigger = 'userid:String,username:String,ua:String,ip:String,dttms:Date'; 3 | 4 | NEWPUBLISH('apps_insert', 'Apps'); 5 | NEWPUBLISH('apps_update', 'Apps'); 6 | NEWPUBLISH('apps_remove', 'Apps'); 7 | NEWPUBLISH('apps_restart', 'Apps'); 8 | NEWPUBLISH('apps_restart_all', trigger); 9 | NEWPUBLISH('apps_stop', 'Apps'); 10 | NEWPUBLISH('apps_stop_all', trigger); 11 | NEWPUBLISH('apps_monitor', 'apps_monitor'); 12 | NEWPUBLISH('apps_analyzator', 'id:String,name:String,type:String,category:String,url:String,dterror:Date,dttms:Date'); 13 | 14 | NEWPUBLISH('notify_apps', notify); 15 | NEWPUBLISH('notify_system', notify); 16 | 17 | NEWPUBLISH('system_monitor', 'ip:String,uptime:Number,processes:Number,hddtotal:Number,hddused:Number,hddfree:Number,memtotal:Number,memused:Number,memfree:Number,dttms:Date'); 18 | 19 | // Helper 20 | FUNC.tms = function($, data) { 21 | if (!data) 22 | return data; 23 | 24 | data.ua = $.ua; 25 | data.ip = $.ip; 26 | data.dttms = NOW; 27 | 28 | if ($.user) { 29 | data.userid = $.user.id; 30 | data.username = $.user.name; 31 | } 32 | 33 | return data; 34 | }; 35 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:19.04 2 | 3 | ENV domain=localhost 4 | 5 | # Install Node.js 14 6 | RUN apt-get update 7 | RUN apt-get -qq update 8 | RUN apt-get install -y build-essential 9 | RUN apt-get install -y curl 10 | RUN curl -sL https://deb.nodesource.com/setup_14.x | bash 11 | RUN apt-get install -y nodejs 12 | 13 | # Install SuperAdmin dependencies 14 | RUN apt-get install -y nginx \ 15 | graphicsmagick \ 16 | zip \ 17 | ftp \ 18 | unzip \ 19 | lsof \ 20 | socat \ 21 | git 22 | RUN curl https://get.acme.sh | sh 23 | RUN npm install -g total4 24 | RUN npm install -g total.js 25 | 26 | RUN rm -rf /var/cache/apk/* 27 | 28 | # Configure and copy SuperAdmin directory 29 | WORKDIR /www 30 | RUN mkdir /www/logs/ 31 | RUN mkdir /www/nginx/ 32 | RUN mkdir /www/acme/ 33 | RUN mkdir /www/ssl/ 34 | RUN mkdir /www/www/ 35 | RUN mkdir /www/superadmin/ 36 | RUN mkdir /www/node_modules/ 37 | RUN npm install total4 38 | RUN npm install total.js 39 | RUN npm install dbms 40 | ADD . /www/superadmin 41 | RUN echo "root:0:0" >> /www/superadmin/user.guid 42 | 43 | # Configure nginx 44 | COPY nginx.conf /etc/nginx/nginx.conf 45 | COPY superadmin.conf /www/nginx/ 46 | RUN sed -i s/#disablehttp#//g /www/nginx/superadmin.conf 47 | RUN sed -i s/#domain#/$domain/g /www/nginx/superadmin.conf 48 | 49 | EXPOSE 80 443 9999 50 | 51 | CMD /www/superadmin/run-docker.sh 52 | 53 | #BUILDING IT 54 | #docker build -t superadmin:1.5 . 55 | 56 | #RUN IT (you can also map any of the exposed ports above) 57 | #docker run -p 8080:80 -it superadmin:1.5 58 | 59 | #EXEC IT (access bash whilst it is running) 60 | #docker exec -it a1ec4787d56b bash 61 | -------------------------------------------------------------------------------- /dockerfile.alpine: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | 3 | ENV domain=localhost 4 | 5 | RUN apk update && \ 6 | apk upgrade -U && \ 7 | apk add nginx graphicsmagick zip unzip lsof socat git acme.sh 8 | 9 | RUN rm -rf /var/cache/* 10 | 11 | # Configure and copy SuperAdmin directory 12 | WORKDIR /www 13 | RUN mkdir /www/logs/ 14 | RUN mkdir /www/nginx/ 15 | RUN mkdir /www/acme/ 16 | RUN mkdir /www/ssl/ 17 | RUN mkdir /www/www/ 18 | RUN mkdir /www/superadmin/ 19 | RUN mkdir /www/node_modules/ 20 | RUN npm install total4 21 | RUN npm install total.js 22 | RUN npm install dbms 23 | ADD . /www/superadmin 24 | RUN echo "root:0:0" >> /www/superadmin/user.guid 25 | 26 | # Configure nginx 27 | COPY nginx.conf /etc/nginx/nginx.conf 28 | COPY superadmin.conf /www/nginx/ 29 | RUN sed -i s/#disablehttp#//g /www/nginx/superadmin.conf 30 | RUN sed -i s/#domain#/$domain/g /www/nginx/superadmin.conf 31 | 32 | EXPOSE 80 443 9999 33 | RUN ["chmod", "+x", "/www/superadmin/run-docker-alpine.sh"] 34 | CMD /www/superadmin/run-docker-alpine.sh 35 | 36 | #BUILDING IT 37 | #docker build -f dockerfile.alpine -t superadmin:3.0 . 38 | 39 | #BUILDING MULTIARCH WITH BUILDX 40 | #BOOTSTRAP: ## Start multicompiler 41 | # docker buildx inspect --bootstrap 42 | 43 | #BUILDX 44 | # docker buildx build --no-cache -f dockerfile.alpine \ 45 | # --platform linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/arm/v7,linux/arm/v6 \ 46 | # -t superadmin:3.0 --pull --push . 47 | 48 | #RUN IT (you can also map any of the exposed ports above) 49 | #docker run -p 9999:9999 -it superadmin:3.0 -------------------------------------------------------------------------------- /ffdhe2048.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz 3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a 4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi 6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD 7 | ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== 8 | -----END DH PARAMETERS----- -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // =================================================== 2 | // Total.js start script 3 | // https://www.totaljs.com 4 | // =================================================== 5 | 6 | const total = 'total4'; 7 | const options = {}; 8 | 9 | // options.ip = '127.0.0.1'; 10 | // options.port = parseInt(process.argv[2]); 11 | // options.unixsocket = require('path').join(require('os').tmpdir(), 'app_name'); 12 | // options.config = { name: 'Total.js' }; 13 | // options.sleep = 3000; 14 | // options.inspector = 9229; 15 | // options.watch = ['private']; 16 | // options.livereload = 'https://yourhostname'; 17 | 18 | var type = process.argv.indexOf('--release', 1) !== -1 || process.argv.indexOf('release', 1) !== -1 ? 'release' : 'debug'; 19 | require(total + '/' + type)(options); -------------------------------------------------------------------------------- /install-alpine.sh: -------------------------------------------------------------------------------- 1 | echo "" 2 | echo "===================================================" 3 | echo -e "\e[41mSuperAdmin Installation\e[0m" 4 | echo "===================================================" 5 | echo "" 6 | echo -e "\e[90mInstallation assumes a clean installation of a Stable Alpine Linux.\e[0m" 7 | echo "" 8 | echo -e "\e[100m-->\e[0m Installation prompts are for creating URL for SuperAdmin." 9 | echo -e "\e[100m-->\e[0m A domain for SuperAdmin has to be mapped to this server when you want to use SSL." 10 | echo -e "\e[100m-->\e[0m \e[90mThis installation installs: Nginx, Node.js, GraphicsMagick and Git.\e[0m" 11 | 12 | # Root check 13 | if [[ $EUID -ne 0 ]]; then 14 | echo -e "\e[91mYou must be a root user.\e[0m" 2>&1 15 | exit 1 16 | fi 17 | 18 | # User Consent 19 | echo "" 20 | read -p $'Do you wish to permit this? \e[104m(y/n)\e[0m : ' userConsent 21 | 22 | if [[ "$userConsent" == "y" || "$userConsent" == "Y" ]]; then 23 | 24 | read -p $'Do you want to provide SuperAdmin via HTTPS? \e[104m(y/n)\e[0m : ' httpsEn 25 | echo "" 26 | 27 | if [ "$httpsEn" == "n" ]; then 28 | httpEn="y" 29 | fi 30 | 31 | #User Input 32 | read -p $'Domain name without protocol (e.g. \e[100msuperadmin.yourdomain.com\e[0m): ' domain 33 | 34 | echo "" 35 | echo "---------------------------------------------------" 36 | echo -e "SuperAdmin URL address will be:" 37 | 38 | if [ "$httpsEn" == "y" ]; then 39 | echo -e "\e[44mhttps://$domain\e[0m" 40 | else 41 | echo -e "\e[44mhttp://$domain\e[0m" 42 | fi 43 | echo "---------------------------------------------------" 44 | echo "" 45 | 46 | read -p $'Are you sure you want to continue? \e[104m(y/n)\e[0m : ' next 47 | 48 | if [ "$next" == "n" ]; then 49 | exit 1; 50 | fi 51 | 52 | #Prerequisits 53 | apk --no-cache update 54 | apk --no-cache add bash ca-certificates coreutils curl git graphicsmagick lftp nginx nodejs nodejs-npm openssl shadow socat sudo tar tzdata unzip zip 55 | ln -s /usr/bin/lftp /usr/bin/ftp 56 | 57 | curl https://get.acme.sh | sh 58 | /root/.acme.sh/acme.sh --set-default-ca --server letsencrypt 59 | 60 | mkdir /www 61 | cd /www 62 | mkdir logs nginx acme ssl www superadmin node_modules 63 | 64 | npm install total4 65 | npm install -g total4 66 | npm install total.js 67 | npm install -g total.js 68 | npm install dbms 69 | npm install pg 70 | 71 | # Total.js downloads package and unpack 72 | cd /www/superadmin/ 73 | wget "https://raw.githubusercontent.com/totaljs/superadmin_templates/main/superadmin.zip" 74 | unzip superadmin.zip 75 | rm superadmin.zip 76 | 77 | cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup 78 | cp /www/superadmin/ffdhe2048.pem /etc/nginx/ffdhe2048.pem 79 | cp /www/superadmin/nginx.conf /etc/nginx/nginx.conf 80 | cp /www/superadmin/superadmin.conf /www/nginx/ 81 | 82 | repexp=s/#domain#/$domain/g 83 | httpenexp=s/#disablehttp#//g 84 | httpsenexp=s/#disablehttps#//g 85 | 86 | if [ "$httpEn" == "y" ]; then 87 | sed -i -e $httpenexp /www/nginx/superadmin.conf 88 | sed -i -e $repexp /www/nginx/superadmin.conf 89 | nginx -s reload 90 | fi 91 | 92 | if [ "$httpsEn" == "y" ]; then 93 | 94 | echo "Generating SSL ..." 95 | 96 | sed -i -e $repexp /www/nginx/superadmin.conf 97 | sed -i -e $httpenexp /www/nginx/superadmin.conf 98 | nginx -s reload 99 | 100 | # Generates SSL 101 | bash /www/superadmin/ssl.sh $domain 102 | 103 | # Copies NGINX configuration file again 104 | cp /www/superadmin/superadmin.conf /www/nginx/ 105 | 106 | sed -i -e $httpsenexp /www/nginx/superadmin.conf 107 | sed -i -e $repexp /www/nginx/superadmin.conf 108 | nginx -s reload 109 | fi 110 | 111 | rm /www/superadmin/user.guid 112 | echo "" 113 | echo "---------------------------------------------------" 114 | read -p $'Which user should SuperAdmin use to run your applications ? (default \e[104mroot\e[0m) : ' user 115 | if id "$user" >/dev/null 2>&1; then 116 | printf "Using user -> %s\n" "$user" 117 | uid=$(id -u ${user}) 118 | gid=$(id -g ${user}) 119 | echo "$user:$uid:$gid" >> /www/superadmin/user.guid 120 | else 121 | printf "User %s does not exist. Using root instead.\n" "$user" 122 | echo "root:0:0" >> /www/superadmin/user.guid 123 | fi 124 | 125 | read -p $'Do you wish to install cron job to start SuperAdmin automatically after server restarts? \e[104m(y/n)\e[0m :' autorestart 126 | 127 | if [ "$autorestart" == "y" ]; then 128 | 129 | # Writes out current crontab 130 | crontab -l > mycron 131 | 132 | # Checks a cron job exists if not add it 133 | 134 | crontab -l | grep '@reboot /bin/bash /www/superadmin/run.sh' || echo '@reboot /bin/bash /www/superadmin/run.sh' >> mycron 135 | crontab mycron 136 | rm mycron 137 | echo "Cron job added." 138 | fi 139 | 140 | service nginx start 141 | 142 | echo "" 143 | echo "---------------------------------------------------" 144 | echo -e "\e[100m--> SuperAdmin uses these commands:\e[0m" 145 | echo "lsof, ps, netstat, du, cat, free, df, tail, last, ifconfig, uptime, tar, git, npm," 146 | echo "wc, grep, cp, mkdir" 147 | echo "---------------------------------------------------" 148 | echo "" 149 | 150 | # Starting 151 | echo -e "\e[42mSTARTING...\e[0m" 152 | /bin/bash /www/superadmin/run.sh 153 | 154 | else 155 | echo -e "\e[41mSorry, this installation cannot continue.\e[0m" 156 | fi 157 | -------------------------------------------------------------------------------- /install-centos.sh: -------------------------------------------------------------------------------- 1 | echo "" 2 | echo "===================================================" 3 | echo -e "\e[41mSuperAdmin Centos 7.3+ Installation\e[0m" 4 | echo "===================================================" 5 | echo "" 6 | 7 | echo -e "\e[31mWARNING" 8 | echo -e "\e[31mA clean installation of CentOS 7.3+ will work best." 9 | echo -e "\e[31mYou may have issues installing on a server with Ngnix or Apache installed." 10 | echo -e "\e[31mThis installation will overwite your nginx.conf file. If you have Nginx installed, make a backup of this file.\e[0m" 11 | echo "" 12 | echo -e "\e[100m-->\e[0m This installation will map SuperAdmin to a domain." 13 | echo -e "\e[100m-->\e[0m Please edit your DNS records accordingly before continuing." 14 | echo -e "\e[100m-->\e[0m This installation installs the following dependencies:" 15 | echo -e "\e[100m-->\e[0m Nginx Repository, EPEL Repository, Nginx, Node.js, GraphicsMagick, zip, ftp, unzip, curl, openssl, and Git.\e[0m" 16 | 17 | echo "" 18 | echo "---------------------------------------------------" 19 | echo -e "\e[100m--> SuperAdmin uses these commands:\e[0m" 20 | echo "lsof, ps, netstat, du, cat, free, df, tail, last, ifconfig, uptime, tar, git, npm," 21 | echo "wc, grep, cp, mkdir" 22 | echo "---------------------------------------------------" 23 | echo "" 24 | 25 | 26 | # Root check 27 | if [[ $EUID -ne 0 ]]; then 28 | echo -e "\e[91mYou must be a root user.\e[0m" 2>&1 29 | exit 1 30 | fi 31 | 32 | # User Consent 33 | echo "" 34 | read -p $'Do you agree to the installation? \e[104m(y/n)\e[0m : ' userConsent 35 | 36 | if [[ "$userConsent" == "y" || "$userConsent" == "Y" ]]; then 37 | 38 | read -p $'Do you want to provide SuperAdmin via HTTPS? \e[104m(y/n)\e[0m : ' httpsEn 39 | echo "" 40 | 41 | if [ "$httpsEn" == "n" ]; then 42 | httpEn="y" 43 | fi 44 | 45 | #User Input 46 | read -p $'Domain name without protocol (e.g. \e[100msuperadmin.yourdomain.com\e[0m): ' domain 47 | if [ "$domain" == "" ]; then 48 | echo -e "No domain entered. A domain is required." 49 | exit 1; 50 | 51 | fi 52 | echo "" 53 | echo "---------------------------------------------------" 54 | echo -e "Your SuperAdmin URL is:" 55 | 56 | if [ "$httpsEn" == "y" ]; then 57 | echo -e "\e[44mhttps://$domain\e[0m" 58 | else 59 | echo -e "\e[44mhttp://$domain\e[0m" 60 | fi 61 | echo "---------------------------------------------------" 62 | echo "" 63 | 64 | read -p $'Are you sure you want to continue? \e[104m(y/n)\e[0m : ' next 65 | 66 | if [ "$next" == "n" ]; then 67 | exit 1; 68 | fi 69 | 70 | #Prerequisits 71 | yum install -y -q epel-release 72 | touch /etc/yum.repos.d/nginx.repo 73 | echo "[nginx]" >> /etc/yum.repos.d/nginx.repo 74 | echo "name=nginx repo" >> /etc/yum.repos.d/nginx.repo 75 | echo "baseurl=http://nginx.org/packages/centos/\$releasever/\$basearch/" >> /etc/yum.repos.d/nginx.repo 76 | echo "gpgcheck=0" >> /etc/yum.repos.d/nginx.repo 77 | echo "enabled=1" >> /etc/yum.repos.d/nginx.repo 78 | curl --silent --location https://rpm.nodesource.com/setup_16.x | bash - 79 | yum update -y -q 80 | yum install -y -q nodejs 81 | yum install -y -q nginx 82 | yum install -y -q graphicsmagick 83 | yum install -y -q zip 84 | yum install -y -q ftp 85 | yum install -y -q unzip 86 | yum install -y -q curl 87 | yum install -y -q openssl 88 | yum install -y -q git 89 | yum install -y -q lsof 90 | yum install -y -q socat 91 | curl https://get.acme.sh | sh 92 | /root/.acme.sh/acme.sh --set-default-ca --server letsencrypt 93 | mkdir /www/ 94 | mkdir /www/logs/ 95 | mkdir /www/nginx/ 96 | mkdir /www/acme/ 97 | mkdir /www/ssl/ 98 | mkdir /www/www/ 99 | mkdir /www/superadmin/ 100 | mkdir /www/node_modules/ 101 | touch /www/superadmin/superadmin.log 102 | cd /www/ 103 | 104 | npm install total4 105 | npm install -g total4 106 | npm install total.js 107 | npm install -g total.js 108 | npm install dbms 109 | npm install pg 110 | 111 | # Total.js downloads package and unpack 112 | cd /www/superadmin/ 113 | wget "https://raw.githubusercontent.com/totaljs/superadmin_templates/main/superadmin.zip" 114 | unzip superadmin.zip 115 | rm superadmin.zip 116 | 117 | cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup 118 | cp /www/superadmin/ffdhe2048.pem /etc/nginx/ffdhe2048.pem 119 | cp /www/superadmin/nginx.conf /etc/nginx/nginx.conf 120 | cp /www/superadmin/superadmin.conf /www/nginx/ 121 | 122 | repexp=s/#domain#/$domain/g 123 | httpenexp=s/#disablehttp#//g 124 | httpsenexp=s/#disablehttps#//g 125 | 126 | if [ "$httpEn" == "y" ]; then 127 | sed -i -e $httpenexp /www/nginx/superadmin.conf 128 | sed -i -e $repexp /www/nginx/superadmin.conf 129 | systemctl start nginx 130 | fi 131 | 132 | if [ "$httpsEn" == "y" ]; then 133 | 134 | echo "Generating SSL ..." 135 | 136 | sed -i -e $repexp /www/nginx/superadmin.conf 137 | sed -i -e $httpenexp /www/nginx/superadmin.conf 138 | systemctl restart nginx 139 | 140 | # Generates SSL 141 | bash /www/superadmin/ssl.sh $domain 142 | 143 | # Copies NGINX configuration file again 144 | cp /www/superadmin/superadmin.conf /www/nginx/ 145 | 146 | sed -i -e $httpsenexp /www/nginx/superadmin.conf 147 | sed -i -e $repexp /www/nginx/superadmin.conf 148 | systemctl restart nginx 149 | fi 150 | 151 | rm /www/superadmin/user.guid 152 | echo "" 153 | echo "---------------------------------------------------" 154 | read -p $'Which user should SuperAdmin use to run your applications ? (default \e[104mroot\e[0m) : ' user 155 | if id "$user" >/dev/null 2>&1; then 156 | printf "Using user -> %s\n" "$user" 157 | uid=$(id -u ${user}) 158 | gid=$(id -g ${user}) 159 | echo "$user:$uid:$gid" >> /www/superadmin/user.guid 160 | else 161 | printf "User %s does not exist. Using root instead.\n" "$user" 162 | echo "root:0:0" >> /www/superadmin/user.guid 163 | fi 164 | 165 | read -p $'Do you wish to install cron job to start SuperAdmin automatically after server restarts? \e[104m(y/n)\e[0m :' autorestart 166 | 167 | if [ "$autorestart" == "y" ]; then 168 | 169 | # Writes out current crontab 170 | crontab -l > mycron 171 | 172 | # Checks a cron job exists if not add it 173 | 174 | crontab -l | grep '@reboot /bin/bash /www/superadmin/run.sh' || echo '@reboot /bin/bash /www/superadmin/run.sh' >> mycron 175 | crontab mycron 176 | rm mycron 177 | echo "Cron job added." 178 | else 179 | echo -e "\e[31mTo Run Manually: $ cd /www/superadmin/ && $ bash run.sh\e[0m" 180 | 181 | fi 182 | 183 | systemctl start nginx 184 | 185 | # Starting 186 | echo -e "\e[42mSTARTING SUPERADMIN...\e[0m" 187 | /bin/bash /www/superadmin/run.sh 188 | echo -e "Your SuperAdmin URL is: \e[44mhttps://$domain\e[0m" 189 | 190 | else 191 | echo -e "\e[41mYou must agree to the installation to continue.\e[0m" 192 | fi -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | echo "" 2 | echo "===================================================" 3 | echo -e "\e[41mSuperAdmin Installation\e[0m" 4 | echo "===================================================" 5 | echo "" 6 | echo -e "\e[90mInstallation assumes a clean installation of Ubuntu Server +16\e[0m" 7 | echo "" 8 | echo -e "\e[100m-->\e[0m Installation prompts are for creating URL for SuperAdmin." 9 | echo -e "\e[100m-->\e[0m A domain for SuperAdmin has to be mapped to this server when you want to use SSL." 10 | echo -e "\e[100m-->\e[0m \e[90mThis installation installs: Nginx, Node.js, GraphicsMagick and Git.\e[0m" 11 | 12 | # Root check 13 | if [[ $EUID -ne 0 ]]; then 14 | echo -e "\e[91mYou must be a root user.\e[0m" 2>&1 15 | exit 1 16 | fi 17 | 18 | # User Consent 19 | echo "" 20 | read -p $'Do you wish to permit this? \e[104m(y/n)\e[0m : ' userConsent 21 | 22 | if [[ "$userConsent" == "y" || "$userConsent" == "Y" ]]; then 23 | 24 | read -p $'Do you want to provide SuperAdmin via HTTPS? \e[104m(y/n)\e[0m : ' httpsEn 25 | echo "" 26 | 27 | if [ "$httpsEn" == "n" ]; then 28 | httpEn="y" 29 | fi 30 | 31 | #User Input 32 | read -p $'Domain name without protocol (e.g. \e[100msuperadmin.yourdomain.com\e[0m): ' domain 33 | 34 | echo "" 35 | echo "---------------------------------------------------" 36 | echo -e "SuperAdmin URL address will be:" 37 | 38 | if [ "$httpsEn" == "y" ]; then 39 | echo -e "\e[44mhttps://$domain\e[0m" 40 | else 41 | echo -e "\e[44mhttp://$domain\e[0m" 42 | fi 43 | echo "---------------------------------------------------" 44 | echo "" 45 | 46 | read -p $'Are you sure you want to continue? \e[104m(y/n)\e[0m : ' next 47 | 48 | if [ "$next" == "n" ]; then 49 | exit 1; 50 | fi 51 | 52 | #Prerequisits 53 | apt-get install -y curl python-software-properties software-properties-common 54 | curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash - 55 | apt-get update 56 | apt-get install -y nginx 57 | apt-get install -y nodejs 58 | apt-get install -y graphicsmagick 59 | apt-get install -y zip 60 | apt-get install -y ftp 61 | apt-get install -y unzip 62 | apt-get install -y lsof 63 | apt-get install -y socat 64 | apt-get install -y sysstat 65 | curl https://get.acme.sh | sh 66 | /root/.acme.sh/acme.sh --set-default-ca --server letsencrypt 67 | mkdir /www/ 68 | mkdir /www/logs/ 69 | mkdir /www/nginx/ 70 | mkdir /www/acme/ 71 | mkdir /www/ssl/ 72 | mkdir /www/www/ 73 | mkdir /www/superadmin/ 74 | mkdir /www/node_modules/ 75 | cd /www/ 76 | npm install total4 77 | npm install -g total4 78 | npm install total.js 79 | npm install -g total.js 80 | npm install dbms 81 | npm install pg 82 | 83 | # Configuration 84 | cd 85 | apt-get install -y git 86 | 87 | # Total.js downloads package and unpack 88 | cd /www/superadmin/ 89 | wget "https://raw.githubusercontent.com/totaljs/superadmin_templates/main/superadmin.zip" 90 | unzip superadmin.zip 91 | rm superadmin.zip 92 | 93 | cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup 94 | cp /www/superadmin/ffdhe2048.pem /etc/nginx/ffdhe2048.pem 95 | cp /www/superadmin/nginx.conf /etc/nginx/nginx.conf 96 | cp /www/superadmin/superadmin.conf /www/nginx/ 97 | 98 | repexp=s/#domain#/$domain/g 99 | httpenexp=s/#disablehttp#//g 100 | httpsenexp=s/#disablehttps#//g 101 | 102 | if [ "$httpEn" == "y" ]; then 103 | sed -i -e $httpenexp /www/nginx/superadmin.conf 104 | sed -i -e $repexp /www/nginx/superadmin.conf 105 | nginx -s reload 106 | fi 107 | 108 | if [ "$httpsEn" == "y" ]; then 109 | 110 | echo "Generating SSL ..." 111 | 112 | sed -i -e $repexp /www/nginx/superadmin.conf 113 | sed -i -e $httpenexp /www/nginx/superadmin.conf 114 | nginx -s reload 115 | 116 | # Generates SSL 117 | bash /www/superadmin/ssl.sh $domain 118 | 119 | # Check if certificate was created successfully 120 | if [ -f "/www/ssl/$domain/fullchain.cer" ]; then 121 | 122 | # Copies NGINX configuration file again 123 | cp /www/superadmin/superadmin.conf /www/nginx/ 124 | 125 | sed -i -e $httpsenexp /www/nginx/superadmin.conf 126 | sed -i -e $repexp /www/nginx/superadmin.conf 127 | nginx -s reload 128 | 129 | echo "Configured superadmin with https://$domain" 130 | else 131 | echo "Failed to generate SSL properly, try 'bash /www/superadmin/reconfigure.sh y $domain y' again later..." 2>&1 132 | fi 133 | 134 | fi 135 | 136 | rm /www/superadmin/user.guid 137 | echo "" 138 | echo "---------------------------------------------------" 139 | read -p $'Which user should SuperAdmin use to run your applications ? (default \e[104mroot\e[0m) : ' user 140 | if id "$user" >/dev/null 2>&1; then 141 | printf "Using user -> %s\n" "$user" 142 | uid=$(id -u ${user}) 143 | gid=$(id -g ${user}) 144 | echo "$user:$uid:$gid" >> /www/superadmin/user.guid 145 | else 146 | printf "User %s does not exist. Using root instead.\n" "$user" 147 | echo "root:0:0" >> /www/superadmin/user.guid 148 | fi 149 | 150 | read -p $'Do you wish to install cron job to start SuperAdmin automatically after server restarts? \e[104m(y/n)\e[0m :' autorestart 151 | 152 | if [ "$autorestart" == "y" ]; then 153 | 154 | # Writes out current crontab 155 | crontab -l > mycron 156 | 157 | # Checks a cron job exists if not add it 158 | 159 | crontab -l | grep '@reboot /bin/bash /www/superadmin/run.sh' || echo '@reboot /bin/bash /www/superadmin/run.sh' >> mycron 160 | crontab mycron 161 | rm mycron 162 | echo "Cron job added." 163 | fi 164 | 165 | service nginx start 166 | 167 | echo "" 168 | echo "---------------------------------------------------" 169 | echo -e "\e[100m--> SuperAdmin uses these commands:\e[0m" 170 | echo "lsof, ps, netstat, du, cat, free, df, tail, last, ifconfig, uptime, tar, git, npm," 171 | echo "wc, grep, cp, mkdir" 172 | echo "---------------------------------------------------" 173 | echo "" 174 | 175 | # Starting 176 | echo -e "\e[42mSTARTING...\e[0m" 177 | /bin/bash /www/superadmin/run.sh 178 | 179 | else 180 | echo -e "\e[41mSorry, this installation cannot continue.\e[0m" 181 | fi -------------------------------------------------------------------------------- /jsonschemas/apps_monitor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "https://www.totaljs.com/files/apps_monitor.json", 3 | "$schema": "https://json-schema.org/draft/2020-12/schema", 4 | "type": "object", 5 | "properties": { 6 | "id": { "type": "string" }, 7 | "url": { "type": "string" }, 8 | "name": { "type": "string" }, 9 | "category": { "type": "string" }, 10 | "port": { "type": "number" }, 11 | "pid": { "type": "string" }, 12 | "cpu": { "type": "number" }, 13 | "memory": { "type": "number" }, 14 | "openfiles": { "type": "number" }, 15 | "hdd": { "type": "number" }, 16 | "connections": { "type": "number" }, 17 | "version": { "type": "string" }, 18 | "analyzator": { "type": "string" }, 19 | "ssl": { "type": "boolean" }, 20 | "dtmonitor": { "type": "date" }, 21 | "dttms": { "type": "date" } 22 | }, 23 | "required": [] 24 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | Copyright 2012-2020 (c) Peter Širka 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a 5 | copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to permit 9 | persons to whom the Software is furnished to do so, subject to the 10 | following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 18 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 21 | USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes auto; 2 | pid /var/run/nginx.pid; 3 | 4 | events { 5 | use epoll; 6 | worker_connections 1024; 7 | multi_accept on; 8 | } 9 | 10 | http { 11 | 12 | limit_req_zone $binary_remote_addr zone=ddos:10m rate=50r/s; 13 | log_format superadmin '{"method":"$request_method","url":"$request_uri","created":"$time_iso8601","ip":"$remote_addr","duration":"$request_time","status":$status,"referrer":"$http_referrer","useragent":"$http_user_agent","response":$body_bytes_sent,"request":$request_length}'; 14 | 15 | server { 16 | location / { 17 | limit_req zone=ddos burst=20 nodelay; 18 | } 19 | } 20 | 21 | underscores_in_headers on; 22 | client_body_buffer_size 10K; 23 | client_header_buffer_size 1k; 24 | client_max_body_size 2m; 25 | large_client_header_buffers 2 1k; 26 | server_names_hash_bucket_size 100; 27 | sendfile on; 28 | # tcp_nopush on; 29 | tcp_nodelay on; 30 | proxy_buffering off; 31 | keepalive_timeout 15; 32 | keepalive_requests 200; 33 | reset_timedout_connection on; 34 | client_body_timeout 10; 35 | send_timeout 5; 36 | server_tokens off; 37 | types_hash_max_size 2048; 38 | include /etc/nginx/mime.types; 39 | default_type application/octet-stream; 40 | access_log off; 41 | # access_log /www/logs/nginx.log; 42 | error_log /var/log/nginx/error.log; 43 | 44 | # TLS configuration based on: 45 | # https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1g&guideline=5.6 46 | # https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28recommended.29 47 | 48 | ssl_session_timeout 1d; 49 | ssl_session_cache shared:SSL:10m; 50 | ssl_session_tickets off; 51 | 52 | # curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam 53 | ssl_dhparam /etc/nginx/ffdhe2048.pem; 54 | 55 | ssl_protocols TLSv1.2 TLSv1.3; 56 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 57 | ssl_prefer_server_ciphers off; 58 | 59 | proxy_http_version 1.1; 60 | 61 | include /www/nginx/*.conf; 62 | } 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SuperAdmin", 3 | "description": "SuperAdmin can manage your Linux server for you. It creates NGINX conf files for your apps build on Total.js framework and runs them for you.", 4 | "version": "9.0.0", 5 | "dependencies": { 6 | "total4": "latest" 7 | }, 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "linux", 13 | "server", 14 | "admin", 15 | "nginx", 16 | "total4", 17 | "total.js" 18 | ], 19 | "author": "Peter Širka", 20 | "license": "MIT" 21 | } -------------------------------------------------------------------------------- /private/backup.sh: -------------------------------------------------------------------------------- 1 | cd $1 2 | tar --exclude="tmp/*" --exclude="dump/*" --exclude="*_backup.package" --exclude="*.tar" --exclude="*.socket" --exclude="*.tar.gz" --exclude=".git/*" --exclude="backups/*" -zcvf "$2" . > /dev/null -------------------------------------------------------------------------------- /private/backuplogs.sh: -------------------------------------------------------------------------------- 1 | cp "$1" "$2" -------------------------------------------------------------------------------- /private/ftp.sh: -------------------------------------------------------------------------------- 1 | HOST="$1" 2 | USER="$2" 3 | PASS="$3" 4 | FROM="$4" 5 | TO="$5" 6 | 7 | ftp -p -inv $HOST << EOF 8 | user $USER $PASS 9 | put "$FROM" "/$TO" 10 | bye 11 | EOF -------------------------------------------------------------------------------- /private/index.js: -------------------------------------------------------------------------------- 1 | // =================================================== 2 | // Total.js start script 3 | // https://www.totaljs.com 4 | // =================================================== 5 | 6 | const total = '{{ value.total }}' || 'total4'; 7 | const options = {}; 8 | 9 | {{ if value.threads }} 10 | options.threads = {{ value.threads }}; 11 | options.logs = 'isolated'; 12 | {{ fi }} 13 | 14 | {{ if value.cluster }} 15 | options.cluster = {{ value.cluster }}; 16 | {{ fi }} 17 | 18 | {{ if !value.debug && value.watcher }} 19 | options.watcher = true; 20 | {{ fi }} 21 | 22 | {{ if value.servicemode }} 23 | options.servicemode = true; 24 | {{ fi }} 25 | 26 | {{ if value.editcode }} 27 | options.edit = '{{ value.editcode }}'; 28 | {{ fi }} 29 | 30 | {{ if value.unixsocket }} 31 | options.unixsocket = '{{ value.unixsocket }}'; 32 | options.unixsocket777 = true; 33 | {{ fi }} 34 | 35 | var type = process.argv.indexOf('--release', 1) !== -1 || process.argv.indexOf('release', 1) !== -1 ? 'release' : 'debug'; 36 | 37 | if (total === 'total.js') { 38 | if (type === 'release') 39 | require(total).http('release', options); 40 | else 41 | require(total + '/debug')(options); 42 | } else 43 | require(total + '/' + type)(options); -------------------------------------------------------------------------------- /private/mkdir.sh: -------------------------------------------------------------------------------- 1 | if [ ! -d "$1" ]; then 2 | mkdir -p $1 3 | fi -------------------------------------------------------------------------------- /private/nginx.conf: -------------------------------------------------------------------------------- 1 | @{nocompress all} 2 | 3 | @{if model.redirect && model.redirect.length} 4 | server { 5 | listen 80; 6 | @{foreach m in model.redirect} 7 | server_name @{m.domain}; 8 | @{end} 9 | 10 | location ~ "^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)$" { 11 | allow all; 12 | default_type "text/plain"; 13 | return 200 "$1.@{model.acmethumbprint}"; 14 | } 15 | 16 | location ~ / { 17 | return 301 http@{if model.isssl}s@{fi}://@{model.domain}$request_uri; 18 | } 19 | } 20 | @{fi} 21 | 22 | @{if model.redirectssl} 23 | server { 24 | listen 443 ssl; 25 | server_name @{model.redirectssl.domain}; 26 | ssl_certificate @{model.redirectssl.ssl_cer}; 27 | ssl_certificate_key @{model.redirectssl.ssl_key}; 28 | ssl_trusted_certificate @{model.redirectssl.ssl_cer}; 29 | ssl_session_timeout 1d; 30 | ssl_session_cache shared:SSL:10m; 31 | ssl_session_tickets off; 32 | ssl_stapling on; 33 | ssl_stapling_verify on; 34 | ssl_protocols TLSv1.2@{if SuperAdmin.nginx > 116001} TLSv1.3@{fi}; 35 | ssl_prefer_server_ciphers off; 36 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 37 | 38 | # HSTS (ngx_http_headers_module is required) (63072000 seconds) 39 | add_header Strict-Transport-Security "max-age=63072000" always; 40 | 41 | location ~ / { 42 | return 301 https://@{model.domain}$request_uri; 43 | } 44 | } 45 | @{fi} 46 | 47 | server { 48 | 49 | charset utf-8; 50 | server_name @{model.domain}; 51 | 52 | @{if model.allow80} 53 | listen 80; 54 | @{fi} 55 | 56 | @{if model.isssl} 57 | listen 443 http2 ssl; 58 | ssl_certificate @{model.ssl_cer}; 59 | ssl_certificate_key @{model.ssl_key}; 60 | ssl_trusted_certificate @{model.ssl_cer}; 61 | ssl_session_timeout 1d; 62 | ssl_session_cache shared:SSL:10m; 63 | ssl_session_tickets off; 64 | ssl_stapling on; 65 | ssl_stapling_verify on; 66 | ssl_protocols TLSv1.2@{if SuperAdmin.nginx > 116001} TLSv1.3@{fi}; 67 | ssl_prefer_server_ciphers off; 68 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 69 | 70 | # HSTS (ngx_http_headers_module is required) (63072000 seconds) 71 | add_header Strict-Transport-Security "max-age=63072000" always; 72 | 73 | @{else} 74 | listen 80; 75 | @{fi} 76 | 77 | location ~ "^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)$" { 78 | allow all; 79 | default_type "text/plain"; 80 | return 200 "$1.@{model.acmethumbprint}"; 81 | } 82 | 83 | client_max_body_size 1M; 84 | 85 | @{if model.islogging} 86 | # flush=1m 87 | access_log @{CONF.directory_console}@{model.linker}--access.log superadmin; 88 | @{fi} 89 | 90 | @{foreach m in model.childs} 91 | location @{m.endpoint} { 92 | @{if m.uploadquote} 93 | client_max_body_size @{m.uploadquote}M; 94 | @{fi} 95 | @{if m.ddosquote} 96 | limit_req zone=ddos burst=@{m.ddosquote} nodelay; 97 | @{fi} 98 | proxy_set_header Host $http_host; 99 | proxy_set_header X-Forwarded-For $remote_addr; 100 | proxy_set_header X-Forwarded-Proto $scheme; 101 | proxy_set_header X-NginX-Proxy true; 102 | proxy_set_header Upgrade $http_upgrade; 103 | proxy_set_header Connection "upgrade"; 104 | proxy_redirect off; 105 | proxy_http_version 1.1; 106 | proxy_buffering off; 107 | @{if m.proxytimeout} 108 | proxy_read_timeout @{m.proxytimeout}s; 109 | @{fi} 110 | proxy_cache_bypass $http_upgrade; 111 | proxy_cache_key sfs$request_uri$scheme; 112 | proxy_pass_header X-Ping; 113 | @{if m.unixsocket} 114 | proxy_pass http://unix:@{m.unixsocket}:/; 115 | @{else} 116 | proxy_pass http://127.0.0.1:@{m.port}; 117 | @{fi} 118 | break; 119 | } 120 | @{end} 121 | 122 | location / { 123 | @{if model.allowedip && model.allowedip.length} 124 | @{foreach m in model.allowedip} 125 | allow @{m}; 126 | @{end} 127 | deny all; 128 | @{fi} 129 | @{if model.blockedip && model.blockedip.length} 130 | @{foreach m in model.blockedip} 131 | deny @{m}; 132 | @{end} 133 | @{fi} 134 | @{if model.uploadquote} 135 | client_max_body_size @{model.uploadquote}M; 136 | @{fi} 137 | @{if model.ddosquote} 138 | limit_req zone=ddos burst=@{model.ddosquote} nodelay; 139 | @{fi} 140 | proxy_set_header Host $http_host; 141 | proxy_set_header X-Forwarded-For $remote_addr; 142 | proxy_set_header X-Forwarded-Proto $scheme; 143 | proxy_set_header X-NginX-Proxy true; 144 | proxy_set_header Upgrade $http_upgrade; 145 | proxy_set_header Connection "upgrade"; 146 | proxy_redirect off; 147 | proxy_http_version 1.1; 148 | proxy_buffering off; 149 | @{if model.proxytimeout} 150 | proxy_read_timeout @{model.proxytimeout}s; 151 | @{fi} 152 | proxy_cache_bypass $http_upgrade; 153 | proxy_cache_key sfs$request_uri$scheme; 154 | proxy_pass_header X-Ping; 155 | @{if model.unixsocket} 156 | proxy_pass http://unix:@{model.unixsocket}:/; 157 | @{else} 158 | proxy_pass http://127.0.0.1:@{model.port}; 159 | @{fi} 160 | break; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /private/npminstall.sh: -------------------------------------------------------------------------------- 1 | cd $1 2 | mkdir node_modules 3 | npm install --no-bin-links -------------------------------------------------------------------------------- /private/superadmin.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID2zCCAsOgAwIBAgIJAK+DUW8lBruTMA0GCSqGSIb3DQEBCwUAMIGDMQswCQYD 3 | VQQGEwJYWDEOMAwGA1UECAwFRWFydGgxDjAMBgNVBAcMBVdvcmxkMQ4wDAYDVQQK 4 | DAVUb3RhbDELMAkGA1UECwwCSVQxEzARBgNVBAMMCnN1cGVyYWRtaW4xIjAgBgkq 5 | hkiG9w0BCQEWE3N1cHBvcnRAdG90YWxqcy5jb20wHhcNMTkxMjEyMTEzMDE2WhcN 6 | MjAxMjExMTEzMDE2WjCBgzELMAkGA1UEBhMCWFgxDjAMBgNVBAgMBUVhcnRoMQ4w 7 | DAYDVQQHDAVXb3JsZDEOMAwGA1UECgwFVG90YWwxCzAJBgNVBAsMAklUMRMwEQYD 8 | VQQDDApzdXBlcmFkbWluMSIwIAYJKoZIhvcNAQkBFhNzdXBwb3J0QHRvdGFsanMu 9 | Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu7IPYJW2IOoV+RMU 10 | 5kIj/gB+ukOKvnvlfqg6SF5n7/waL3ss3WC84ZC9A3S1CW79w3/f13Db4h6CLO4p 11 | 183Djfq9mUVarndMkbyYWnGOKa89ryZm+xyogFL2fWaGaaYIML3Pllec463awUnr 12 | Ahgt+1Lg8grkacm6ZQAJf2OBJy0Kh/XEomq7lP3szKlOnOqtyN/I2sJi+1jizemj 13 | eqRs72kLr+iJK13HtGpTa39U6y+SBGlsvmFxKd62vDSp/04mcPszNcbtksGIWDJP 14 | WQ7lLzGLLKJ8nMPJxkbAbJKHbthmN0T1BXqDFteYe4kqJK1ZecQmQiyKfmOIRmxV 15 | b7gL/QIDAQABo1AwTjAdBgNVHQ4EFgQUP/lnijYk27fUo/SS0Pi/R8fszekwHwYD 16 | VR0jBBgwFoAUP/lnijYk27fUo/SS0Pi/R8fszekwDAYDVR0TBAUwAwEB/zANBgkq 17 | hkiG9w0BAQsFAAOCAQEAbZLpv5Pt/CYmtiWGMnIpRDq4wGuKgvRiSd9bBTiTQixI 18 | 5yAd8ivpAqmlxaANqY4XYTKNYstlad+Mz8A85F4PLmtHOQq39cPmyyeU8KrYDXG+ 19 | 4/Bf02wzormzSYz003kYCja+Hl7TbSJQatJfw7M1paCgZY99A6YtYa7rZmttM02D 20 | QRKyZ//J8lNYV56FccOY+nX50dcD33w8z/Dg792u3U2nVJN2gs+TwVMxZNhOOeR0 21 | IJd5K6LQssOCyEtpeMwg2WAJTOnfQyCeC9+aGvpGjnTqawPJEhbtpyDWEb+8siUf 22 | x3WEjrOxkL8rOpSrtr+kRtGPGqIhzSlFiBkxQAnpYw== 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /private/superadmin.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7sg9glbYg6hX5 3 | ExTmQiP+AH66Q4q+e+V+qDpIXmfv/BoveyzdYLzhkL0DdLUJbv3Df9/XcNviHoIs 4 | 7inXzcON+r2ZRVqud0yRvJhacY4prz2vJmb7HKiAUvZ9ZoZppggwvc+WV5zjrdrB 5 | SesCGC37UuDyCuRpybplAAl/Y4EnLQqH9cSiaruU/ezMqU6c6q3I38jawmL7WOLN 6 | 6aN6pGzvaQuv6IkrXce0alNrf1TrL5IEaWy+YXEp3ra8NKn/TiZw+zM1xu2SwYhY 7 | Mk9ZDuUvMYssonycw8nGRsBskodu2GY3RPUFeoMW15h7iSokrVl5xCZCLIp+Y4hG 8 | bFVvuAv9AgMBAAECggEAJkG+3KLnQoI0BFadVel5SJZ1PiXyK605M726U3HaoZGz 9 | DKaAmIbVOOy1efTZyZhz5Ns1lsYMK2Soh9vrb28jQtcuugTm3NS4KKRWMWkf3uM3 10 | IXIkX16SAcXnM9wn3XTYCodFvpERC18uTWNxoTWoeaeE7hDGScGyWk/Nn8d6Va+A 11 | 83P9TiQwCKXtgdiEoyP8IFNNKxNLI4eNd6zKfj0VkQ/vtZY1uxBzvVXB8SZZZmwW 12 | rOx8qyA0RKxcIH7yiibnBPNZl9I9v/cSfxo6GbPPRoDORxtHIyhVsB34ldGydabO 13 | ZJivvR277WY5X+QiygGzspczpwpuv6zVT0KjEnmtiQKBgQDrwWjGjV4Ez6FNNctJ 14 | 8HQ4FNpPzDxicW2ojmB41SHvA/cvGi9lIwltdKl2hTR3E69pE3u2/6bVeLxEXAJC 15 | 4lipGexvjC4g2jIgJNuIb9QZzcDV+xeZA1G3ZH7RU+geC3FZW/JfoL7djFobiNwo 16 | ZYRsykS6i6439Rr7M5wccHj+ywKBgQDL0CdAqEgLRURIDM4t2H2uALDGeWoYLAmk 17 | SABnuS4/jjMaJog1JCclqmUAfWshDmXOiq6+OSKlGqR7Z7BVE88ZiK/QDwTRvPPI 18 | NilVfyLvXZsVtTYjhiNKEMKEH7Z64PuTGL5R+fVP2o6Hmeo+YW+SG7jLdEwnVlog 19 | EFKwQYC/VwKBgQCRP2rHEWfAfQ96206w8jUYLDjIeyMk349ZDsb/CwjKEl5jJbXX 20 | kLuJNRwHClBloLosebV57I4j7SvIzgsSnBNVs8QIgYwV73h8d1jN+V/queo5q2ZB 21 | flsFKyQ64YAbqp8eF9j+87FEG+mUF0DUnp70y7WGXPJsRVVnOMfQebcB+wKBgEaG 22 | su6GzNdJksUz2Sy4G1VY29LyNKpEotl8KNe7lqtbc4d7CesJpZo9ClJPFAEOUGBW 23 | UP12G+zx+TTSvovSHPoC+IOQjuRc9Ca1xzvUkKwvwfiTCjbktdVtU0IRDz4aGDPg 24 | fgsz6ZVGVcfKlOodIVR3z0xn4+pA0VMDEvn20KxlAoGBAMLbzloYEFUKlHp88KPC 25 | qkbyMzz/GruVRiaAuMAp4tJlL53pRHwyF05BM+ahBLe8T5Rwt0GnK4xfhobiVVHH 26 | nuGSpvvi+2B94IV6k6vTyXQdQI9IVStkd/79lIO6kt62iNv8j7tFDYB4kH8ODZHk 27 | V5jbPJPYUrcu1eu/T4sV+pa9 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /private/update-total.sh: -------------------------------------------------------------------------------- 1 | cd /www/ 2 | npm install total.js 3 | npm install total4 4 | npm install dbms -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totaljs/superadmin/46d7d59e3a834bdeee9a6436d0c82bfb50e74294/public/favicon.ico -------------------------------------------------------------------------------- /public/forms/alarm.html: -------------------------------------------------------------------------------- 1 | 71 | 72 | -------------------------------------------------------------------------------- /public/forms/analyzator.html: -------------------------------------------------------------------------------- 1 | 30 | 31 | -------------------------------------------------------------------------------- /public/forms/app.html: -------------------------------------------------------------------------------- 1 | 129 | 130 | 182 | -------------------------------------------------------------------------------- /public/forms/filebrowser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 40 | 41 | -------------------------------------------------------------------------------- /public/forms/intro.html: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /public/forms/profile.html: -------------------------------------------------------------------------------- 1 | 27 | 28 | -------------------------------------------------------------------------------- /public/forms/service.html: -------------------------------------------------------------------------------- 1 | 37 | 38 | 71 | -------------------------------------------------------------------------------- /public/forms/summary.html: -------------------------------------------------------------------------------- 1 | 129 | 130 | -------------------------------------------------------------------------------- /public/forms/terminal.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /public/forms/upload.html: -------------------------------------------------------------------------------- 1 | 29 | 30 | -------------------------------------------------------------------------------- /public/forms/user.html: -------------------------------------------------------------------------------- 1 | 29 | 30 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/totaljs/superadmin/46d7d59e3a834bdeee9a6436d0c82bfb50e74294/public/icon.png -------------------------------------------------------------------------------- /public/js/helpers.js: -------------------------------------------------------------------------------- 1 | Thelpers.app_uptime = function(value) { 2 | if (value) { 3 | var arr = value.split('-'); 4 | return arr.length === 1 ? arr[0] : '{0}'.format(arr[0].parseInt().pluralize('# days', '# day', '# days', '# days')); 5 | } else 6 | return DEF.empty; 7 | }; 8 | 9 | Thelpers.domain = function(val) { 10 | return val.substring(val.indexOf('/') + 2); 11 | }; 12 | 13 | Thelpers.filesize = function(value, decimals, type) { 14 | return value ? value.filesize(decimals, type) : '...'; 15 | }; 16 | 17 | Number.prototype.filesize = function(decimals, type) { 18 | 19 | if (typeof(decimals) === 'string') { 20 | var tmp = type; 21 | type = decimals; 22 | decimals = tmp; 23 | } 24 | 25 | var value; 26 | var t = this; 27 | 28 | // this === bytes 29 | switch (type) { 30 | case 'bytes': 31 | value = t; 32 | break; 33 | case 'KB': 34 | value = t / 1024; 35 | break; 36 | case 'MB': 37 | value = filesizehelper(t, 2); 38 | break; 39 | case 'GB': 40 | value = filesizehelper(t, 3); 41 | break; 42 | case 'TB': 43 | value = filesizehelper(t, 4); 44 | break; 45 | default: 46 | 47 | type = 'bytes'; 48 | value = t; 49 | 50 | if (value > 1023) { 51 | value = value / 1024; 52 | type = 'KB'; 53 | } 54 | 55 | if (value > 1023) { 56 | value = value / 1024; 57 | type = 'MB'; 58 | } 59 | 60 | if (value > 1023) { 61 | value = value / 1024; 62 | type = 'GB'; 63 | } 64 | 65 | if (value > 1023) { 66 | value = value / 1024; 67 | type = 'TB'; 68 | } 69 | 70 | break; 71 | } 72 | 73 | type = ' ' + type; 74 | return (decimals === undefined ? value.format(1) : value.format(decimals)) + type; 75 | }; 76 | 77 | function filesizehelper(number, count) { 78 | while (count--) { 79 | number = number / 1024; 80 | if (number.toFixed(3) === '0.000') 81 | return 0; 82 | } 83 | return number; 84 | } 85 | 86 | Thelpers.app_trending = function(val, type) { 87 | 88 | var key = this.path.substring(9); 89 | var obj = W.appsinfo[key]; 90 | if (!obj) 91 | return DEF.empty; 92 | 93 | key += '_' + type; 94 | 95 | var prev = W.apps.trending[key]; 96 | var curr = obj[type]; 97 | 98 | W.apps.trending[key] = curr; 99 | 100 | if (type === 'connections') 101 | val = '' + val + ''; 102 | 103 | if (prev == null) 104 | return val; 105 | 106 | var plus = ''; 107 | 108 | if (prev < curr) 109 | plus = ''; 110 | else if (prev > curr) 111 | plus = ''; 112 | 113 | return plus + val; 114 | }; 115 | 116 | Thelpers.uptime = function(value) { 117 | // value === seconds 118 | var minutes = (value / 60); 119 | var hours = (minutes / 60); 120 | var days = hours / 24; 121 | return days ? Math.round(days).pluralize('# days', '# day', '# days', '# days') : hours.padLeft(2) + ':' + minutes.padLeft(2); 122 | }; 123 | 124 | Thelpers.counter = function(value) { 125 | if (value > 999999) 126 | return (value / 1000000).format(2) + ' M'; 127 | if (value > 9999) 128 | return (value / 10000).format(2) + ' K'; 129 | return value.format(0); 130 | }; 131 | 132 | Thelpers.indexer = function(index) { 133 | return index + 1; 134 | }; 135 | 136 | Thelpers.checkbox = function(val) { 137 | return ''; 138 | }; 139 | 140 | Thelpers.progress = function(val) { 141 | 142 | if (!val) 143 | val = 0; 144 | 145 | var color = val < 30 ? '#68B25B' : val < 50 ? '#CCCB41' : val < 70 ? '#EC8632' : '#E73323'; 146 | return '
'.format(val, color); 147 | }; -------------------------------------------------------------------------------- /public/js/syntax.js: -------------------------------------------------------------------------------- 1 | WAIT(function() { 2 | return CodeMirror.defineSimpleMode; 3 | }, function() { 4 | CodeMirror.defineSimpleMode('totaljs-tags', { 5 | start: [ 6 | { regex: /@{/, push: 'totaljs', token: 'variable-T' }, 7 | { regex: /{\{/, push: 'tangular', token: 'variable-A' }, 8 | { regex: /@\(/, push: 'localization', token: 'variable-L' } 9 | ], 10 | 11 | tangular: [ 12 | { regex: /\}\}/, pop: true, token: 'variable-A' }, 13 | { regex: /./, token: 'variable-A' } 14 | ], 15 | 16 | totaljs: [ 17 | { regex: /\}/, pop: true, token: 'variable-T' }, 18 | { regex: /./, token: 'variable-T' } 19 | ], 20 | 21 | localization: [ 22 | { regex: /\)/, pop: true, token: 'variable-L' }, 23 | { regex: /./, token: 'variable-L' } 24 | ] 25 | }); 26 | 27 | CodeMirror.defineMode('totaljs', function(config, parserConfig) { 28 | var totaljs = CodeMirror.getMode(config, 'totaljs-tags'); 29 | if (!parserConfig || !parserConfig.base) return totaljs; 30 | return CodeMirror.multiplexingMode(CodeMirror.getMode(config, parserConfig.base), { open: /(@\{|\{\{|@\()/, close: /(\}\}|\}|\))/, mode: totaljs, parseDelimiters: true }); 31 | }); 32 | 33 | CodeMirror.defineMIME('text/totaljs', 'totaljs'); 34 | }); 35 | 36 | CodeMirror.defineMode('totaljsresources', function() { 37 | 38 | var REG_KEY = /^[a-z0-9_\-.#]+/i; 39 | return { 40 | 41 | startState: function() { 42 | return { type: 0, keyword: 0 }; 43 | }, 44 | 45 | token: function(stream, state) { 46 | 47 | var m; 48 | 49 | if (stream.sol()) { 50 | 51 | var line = stream.string; 52 | if (line.substring(0, 2) === '//') { 53 | stream.skipToEnd(); 54 | return 'comment'; 55 | } 56 | 57 | state.type = 0; 58 | } 59 | 60 | m = stream.match(REG_KEY, true); 61 | if (m) 62 | return 'tag'; 63 | 64 | if (!stream.string) { 65 | stream.next(); 66 | return ''; 67 | } 68 | 69 | var count = 0; 70 | 71 | while (true) { 72 | 73 | count++; 74 | if (count > 5000) 75 | break; 76 | 77 | var c = stream.peek(); 78 | if (c === ':') { 79 | stream.skipToEnd(); 80 | return 'def'; 81 | } 82 | 83 | if (c === '(') { 84 | if (stream.skipTo(')')) { 85 | stream.eat(')'); 86 | return 'variable-L'; 87 | } 88 | } 89 | 90 | } 91 | 92 | stream.next(); 93 | return ''; 94 | } 95 | }; 96 | }); -------------------------------------------------------------------------------- /public/parts/alarms.html: -------------------------------------------------------------------------------- 1 | 65 | 66 |
67 | 68 | -------------------------------------------------------------------------------- /public/parts/settings.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 7 |
8 |
9 |
10 | 11 |
12 | 149 |
150 |
151 |
152 |
153 | 154 | -------------------------------------------------------------------------------- /public/parts/users.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 7 |
8 |
9 | 10 |
11 |
12 | 41 |
42 |
43 | 44 |
45 |
46 | 47 |
48 | 49 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![MIT License][license-image]][license-url] 2 | 3 | # SuperAdmin v9.0.0 4 | 5 | - License: [MIT](license.txt) 6 | - [__Documentation__](https://docs.totaljs.com/superadmin/) 7 | - [Chat support](https://platform.totaljs.com/?open=messenger) 8 | - [Join to __Total.js Telegram__](https://t.me/totalplatform) 9 | 10 | ## Contact 11 | 12 | - (c) 2012-2020 by Peter Širka - 13 | - contact form 14 | - 15 | 16 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat 17 | [license-url]: license.txt 18 | -------------------------------------------------------------------------------- /reconfigure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Reconfigures SuperAdmin from/to HTTPs 4 | 5 | # $1 = SuperAdmin via HTTPS (y/n) 6 | # $2 = Domain name without protocol (e.g. superadmin.yourdomain.com) 7 | # $3 = Generate new SSL (y/n) 8 | 9 | repexp=s/#domain#/$2/g 10 | httpenexp=s/#disablehttp#//g 11 | httpsenexp=s/#disablehttps#//g 12 | 13 | cp /www/superadmin/superadmin.conf /www/nginx/ 14 | 15 | if [ "$1" == "n" ]; then 16 | sed -i -e $httpenexp /www/nginx/superadmin.conf 17 | sed -i -e $repexp /www/nginx/superadmin.conf 18 | nginx -s reload 19 | 20 | echo "Configured superadmin with http://$2" 21 | else 22 | 23 | if [ "$3" == "y" ]; then 24 | # ensure it's not configured with ssl 25 | sed -i -e $repexp /www/nginx/superadmin.conf 26 | sed -i -e $httpenexp /www/nginx/superadmin.conf 27 | nginx -s reload 28 | 29 | # Generates SSL 30 | echo "Generating SSL ..." 31 | bash /www/superadmin/ssl.sh $2 32 | 33 | fi 34 | 35 | if [ -f "/www/ssl/$2/fullchain.cer" ]; then 36 | # copy it again to it's reconfigured with ssl 37 | cp /www/superadmin/superadmin.conf /www/nginx/ 38 | 39 | sed -i -e $httpsenexp /www/nginx/superadmin.conf 40 | sed -i -e $repexp /www/nginx/superadmin.conf 41 | nginx -s reload 42 | 43 | echo "Configured superadmin with https://$2" 44 | else 45 | echo "Failed to reconfigure with HTTPS properly, '/www/ssl/$2/fullchain.cer' not found..." 2>&1 46 | fi 47 | fi 48 | 49 | /bin/bash /www/superadmin/run.sh -------------------------------------------------------------------------------- /resources/default.resource: -------------------------------------------------------------------------------- 1 | 401 : Invalid permissions 2 | 404 : The requested record not found 3 | error-file : File not found in the request 4 | error-disabled : Your account was disabled 5 | error-credentials : Invalid credentials 6 | error-url-exists : The URL address is already registered in the database 7 | error-port : The PORT number is already in use 8 | url : Invalid URL address 9 | error-app-linker : The URL address is already registered in the database 10 | error-file-invalid : Not allowed file for editing 11 | error-template : Invalid template 12 | filename : Invalid file name 13 | error-restore : Backup not found -------------------------------------------------------------------------------- /run-docker-alpine.sh: -------------------------------------------------------------------------------- 1 | node --nouse-idle-notification --expose-gc /www/superadmin/index.js 9999 --release > /dev/stdout & 2 | nginx -g "daemon off;" -------------------------------------------------------------------------------- /run-docker.sh: -------------------------------------------------------------------------------- 1 | /usr/bin/node --nouse-idle-notification --expose-gc /www/superadmin/index.js 9999 --release > /dev/stdout & 2 | nginx -g "daemon off;" -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SA_PID=$(lsof -i :9999 | grep "LISTEN" | awk {'print $2'}) 4 | 5 | start_superadmin() { 6 | echo "STARTING SUPERADMIN ON PORT 9999" 7 | cp /www/superadmin/logs/debug.log "/www/logs/superadmin_$(date +%FT%H%M).log" 2>/dev/null 8 | mkdir -p /www/superadmin/logs/ 9 | /usr/bin/node --nouse-idle-notification --expose-gc /www/superadmin/index.js 9999 > /www/superadmin/logs/debug.log & 10 | } 11 | 12 | if [[ $SA_PID ]] 13 | then 14 | echo "KILLING OLD INSTANCE OF SUPERADMIN" 15 | kill -9 $SA_PID 16 | start_superadmin 17 | else 18 | echo "SUPERADMIN IS CURRENTLY NOT RUNNING." 19 | start_superadmin 20 | exit 1 21 | fi 22 | -------------------------------------------------------------------------------- /schemas/account-login.js: -------------------------------------------------------------------------------- 1 | NEWSCHEMA('Account/Login', function(schema) { 2 | 3 | schema.define('login', String, true); 4 | schema.define('password', String, true); 5 | 6 | schema.addWorkflow('exec', function($, model) { 7 | 8 | if (BLOCKED($, 5)) { 9 | $.invalid('401'); 10 | return; 11 | } 12 | 13 | model.password = model.password.sha256(CONF.session_secret); 14 | 15 | NOSQL('users').read().fields('id,isdisabled').where('login', model.login).where('password', model.password).callback(function(err, response) { 16 | 17 | if (response) { 18 | 19 | if (response.isdisabled) { 20 | $.invalid('error-disabled'); 21 | return; 22 | } 23 | 24 | var session = {}; 25 | session.id = UID(); 26 | session.userid = response.id; 27 | session.dtcreated = NOW; 28 | session.ua = $.ua; 29 | session.ip = $.ip; 30 | 31 | PREF.set('credentials', null); 32 | 33 | NOSQL('sessions').insert(session).callback(function() { 34 | MAIN.session.authcookie($, session.id, session.userid, '1 week'); 35 | $.success(); 36 | }); 37 | 38 | } else 39 | $.invalid('error-credentials'); 40 | 41 | }); 42 | 43 | }); 44 | 45 | }); -------------------------------------------------------------------------------- /schemas/account.js: -------------------------------------------------------------------------------- 1 | NEWSCHEMA('Account', function(schema) { 2 | 3 | schema.define('name', String, true); 4 | schema.define('login', String, true); 5 | schema.define('password', String); 6 | 7 | schema.setRead(function($) { 8 | NOSQL('users').read().fields('-password').id($.user.id).callback($.callback); 9 | }); 10 | 11 | schema.setSave(function($, model) { 12 | model.password = model.password ? model.password.sha256(CONF.session_secret) : undefined; 13 | NOSQL('users').modify(model).id($.user.id).callback($.done()); 14 | }); 15 | 16 | schema.addWorkflow('logout', function($) { 17 | MAIN.session.logout($); 18 | $.success(); 19 | }); 20 | 21 | }); -------------------------------------------------------------------------------- /schemas/alarms.js: -------------------------------------------------------------------------------- 1 | NEWSCHEMA('AlarmRule', function(schema) { 2 | schema.define('name', 'String', true); 3 | schema.define('value', Object); 4 | schema.define('comparer', ['==', '!=', '<', '<=', '>', '>='], true); 5 | }); 6 | 7 | NEWSCHEMA('Alarms', function(schema) { 8 | 9 | schema.define('id', UID); 10 | schema.define('appid', UID); 11 | schema.define('operator', ['or', 'and'])('or'); 12 | schema.define('sysoperator', ['or', 'and'])('or'); 13 | schema.define('name', 'String(50)', true); 14 | schema.define('type', ['apps', 'system']); 15 | schema.define('sysrules', '[AlarmRule]'); 16 | schema.define('rules', '[AlarmRule]'); 17 | schema.define('message', 'String(100)', true); 18 | schema.define('phone', '[Phone]'); 19 | schema.define('email', '[Email]'); 20 | schema.define('delay', 'String(30)'); 21 | schema.define('highpriority', Boolean); 22 | schema.define('debug', Boolean); 23 | schema.define('each', Boolean); // notify for all or each app 24 | schema.define('isenabled', Boolean); 25 | 26 | schema.setQuery(function($) { 27 | 28 | if (!$.user.sa) { 29 | $.invalid('401'); 30 | return; 31 | } 32 | 33 | NOSQL('alarms').find().sort('datecreated_desc').callback($.callback); 34 | }); 35 | 36 | schema.setRemove(function($) { 37 | 38 | if (!$.user.sa) { 39 | $.invalid('401'); 40 | return; 41 | } 42 | 43 | NOSQL('alarms').remove().id($.id).callback($.done()); 44 | }); 45 | 46 | schema.setSave(function($, model) { 47 | 48 | if (!$.user.sa) { 49 | $.invalid('401'); 50 | return; 51 | } 52 | 53 | var db = NOSQL('alarms'); 54 | 55 | if (model.id) { 56 | model.dateupdated = NOW; 57 | db.modify(model).where('id', model.id).callback($.done()); 58 | } else { 59 | model.id = UID(); 60 | model.datecreated = NOW; 61 | db.insert(model).callback($.done()); 62 | } 63 | 64 | setTimeout2('alarms', refresh, 2000); 65 | }); 66 | 67 | }); 68 | 69 | function refresh() { 70 | 71 | MAIN.rules = []; 72 | MAIN.sysrules = []; 73 | 74 | NOSQL('alarms').find().callback(function(err, items) { 75 | 76 | var rules = []; 77 | var sysrules = []; 78 | 79 | for (var i = 0; i < items.length; i++) { 80 | var item = items[i]; 81 | if (item.isenabled) { 82 | 83 | var builder = []; 84 | var arr = item.type === 'apps' ? item.rules : item.sysrules; 85 | 86 | for (var j = 0; j < arr.length; j++) { 87 | var rule = arr[j]; 88 | var toBytes = (/mem|hdd/i).test(rule.name); 89 | builder.push(rule.name + rule.comparer + (typeof(rule.value) === 'string' ? ('\'' + rule.value.replace(/[\s;.\-,"'`]+/g, '') + '\'') : (toBytes ? (rule.value * 1024 * 1024).floor(1) : rule.value))); 90 | } 91 | 92 | if (builder.length) { 93 | if (item.type === 'apps') 94 | rules.push({ id: item.id, each: item.each, debug: item.debug, delay: item.delay || '5 minutes', appid: item.appid, appsnotified: item.appsnotified || {}, name: item.name, phone: item.phone, email: item.email, message: item.message, validate: new Function('app', 'return ' + builder.join(item.operator === 'and' ? '&&' : '||')) }); 95 | else 96 | sysrules.push({ id: item.id, delay: item.delay || '5 minutes', name: item.name, phone: item.phone, email: item.email, message: item.message, validate: new Function('sys', 'return ' + builder.join(item.sysoperator === 'and' ? '&&' : '||')) }); 97 | } 98 | 99 | } 100 | } 101 | 102 | MAIN.rules = rules; 103 | MAIN.sysrules = sysrules; 104 | }); 105 | } 106 | 107 | ON('ready', refresh); -------------------------------------------------------------------------------- /schemas/apps-build.js: -------------------------------------------------------------------------------- 1 | const Fs = require('fs'); 2 | const Path = require('path'); 3 | 4 | NEWSCHEMA('Apps/Build', function(schema) { 5 | 6 | schema.define('design', String, true); 7 | schema.define('compiled', String, true); 8 | 9 | schema.setRead(function($) { 10 | 11 | var item = APPLICATIONS.findItem('id', $.id); 12 | 13 | if (!item) { 14 | $.invalid(404); 15 | return; 16 | } 17 | 18 | var filename = Path.join(CONF.directory_www, item.linker, 'builds', 'app.build'); 19 | Fs.readFile(filename, function(err, data) { 20 | if (err) { 21 | $.callback(null); 22 | } else { 23 | $.controller.binary(data, 'application/json'); 24 | $.cancel(); 25 | } 26 | }); 27 | }); 28 | 29 | schema.setSave(function($, model) { 30 | 31 | var item = APPLICATIONS.findItem('id', $.id); 32 | 33 | if (!item) { 34 | $.invalid(404); 35 | return; 36 | } 37 | 38 | var directory = Path.join(CONF.directory_www, item.linker, 'builds'); 39 | var filename = Path.join(CONF.directory_www, item.linker, 'builds', 'app.build'); 40 | 41 | Fs.mkdir(directory, function() { 42 | Fs.writeFile(filename, JSON.stringify(model), $.successful(function() { 43 | if (item.debug || item.stopped) 44 | $.success(); 45 | else 46 | $ACTION('GET *Apps --> restart', null, $.done(), $); 47 | })); 48 | }); 49 | }); 50 | 51 | }); -------------------------------------------------------------------------------- /schemas/apps.js: -------------------------------------------------------------------------------- 1 | const Fs = require('fs'); 2 | const Path = require('path'); 3 | const Exec = require('child_process').exec; 4 | 5 | NEWSCHEMA('Apps', function(schema) { 6 | 7 | schema.define('id', UID); 8 | schema.define('url', 'String'); 9 | schema.define('name', 'String(50)'); 10 | schema.define('category', 'String(50)'); 11 | schema.define('redirect', '[String]'); 12 | schema.define('allow', '[String]'); 13 | schema.define('disallow', '[String]'); 14 | schema.define('servicemode', Boolean); 15 | schema.define('ssl_key', String); 16 | schema.define('ssl_cer', String); 17 | schema.define('threads', String); 18 | schema.define('note', String); 19 | schema.define('startscript', String); // A start script 20 | schema.define('nginx', String); // Additional NGINX settings (lua) 21 | schema.define('delay', Number); // Delay after start 22 | schema.define('memory', Number); // Memory limit 23 | schema.define('priority', Number); // Start priority 24 | schema.define('port', Number); 25 | schema.define('cluster', 'String(4)'); // Thread count number or string - "auto" 26 | schema.define('ddos', Number); // Maximum count of request per second 27 | schema.define('size', Number); // Maximum size of request body (upload size) 28 | schema.define('proxytimeout', Number); // Sets the "proxy_read_timeout" 29 | schema.define('debug', Boolean); // Enables debug mode 30 | schema.define('subprocess', Boolean); 31 | schema.define('accesslog', Boolean); // Enables access_log 32 | schema.define('backup', Boolean); // Enables backup 33 | schema.define('watcher', Boolean); // Enables Total.js watcher for release mode 34 | schema.define('version', String); // Total.js Version 35 | schema.define('highpriority', Boolean); // App with high priority 36 | schema.define('unixsocket', Boolean); // Enables unixsocket 37 | schema.define('allow80', Boolean); // Disables redirect from HTTP to HTTPS 38 | schema.define('editcode', String); // A link to the Code Editor 39 | 40 | schema.required('name', model => model.servicemode); 41 | schema.required('url', model => !model.servicemode); 42 | 43 | // TMS - Fields are added only for TMS of this schema 44 | schema.jsonschema_define('userid', 'String'); 45 | schema.jsonschema_define('username', 'String'); 46 | schema.jsonschema_define('ua', 'String'); 47 | schema.jsonschema_define('ip', 'String'); 48 | schema.jsonschema_define('dtupdated', 'Date'); 49 | schema.jsonschema_define('dttms', 'Date'); 50 | 51 | function reset_alarms(appid) { 52 | MAIN.rules.wait(function(item, next) { 53 | delete item.appsnotified[appid]; 54 | NOSQL('alarms').modify({ appsnotified: item.appsnotified }).id(item.id).callback(next); 55 | }); 56 | } 57 | 58 | schema.setQuery(function($) { 59 | $.callback(APPLICATIONS); 60 | }); 61 | 62 | schema.setRead(function($) { 63 | var item = APPLICATIONS.findItem('id', $.id); 64 | if (item) { 65 | item = CLONE(item); 66 | if (item.subprocess) 67 | item.url += item.path; 68 | $.callback(item); 69 | } else 70 | $.invalid('404'); 71 | }); 72 | 73 | var obtaininginfo = false; 74 | 75 | // Reads info 76 | schema.addWorkflow('info', function($) { 77 | 78 | if (obtaininginfo) { 79 | $.success(); 80 | return; 81 | } 82 | 83 | obtaininginfo = true; 84 | 85 | APPLICATIONS.wait(function(item, next) { 86 | 87 | if (MAIN.restarting) { 88 | next(); 89 | return; 90 | } 91 | 92 | if (item.stopped) { 93 | 94 | if (MAIN.ws) { 95 | var current = {}; 96 | current.id = item.id; 97 | current.TYPE = 'appinfo'; 98 | current.is = false; 99 | MAIN.ws.send(current); 100 | } 101 | 102 | return setImmediate(next); 103 | } 104 | 105 | SuperAdmin.pid2(item, function(err, pid) { 106 | if (err) { 107 | SuperAdmin.run(item.port, () => next()); 108 | SuperAdmin.notify(item, 0); 109 | SuperAdmin.wsnotify('app_restart', item); 110 | EMIT('superadmin_app_restart', item); 111 | } else 112 | SuperAdmin.appinfo(pid, next, item); 113 | }); 114 | 115 | }, function() { 116 | obtaininginfo = false; 117 | SuperAdmin.savestats(); 118 | $.success(); 119 | }, 2); 120 | 121 | }); 122 | 123 | // Analyzes logs 124 | schema.addWorkflow('analyzator', function($) { 125 | 126 | var output = []; 127 | var search = $.query && $.query.q ? [$.query.q.toLowerCase()] : ['\n======= ', 'obsolete', 'error', 'port is already in use', 'deprecationwarning']; 128 | var length = search.length; 129 | 130 | APPLICATIONS.wait(function(item, next) { 131 | 132 | if (item.stopped) 133 | return next(); 134 | 135 | var type = 0; 136 | var filename = Path.join(CONF.directory_www, item.linker, 'logs', 'debug.log'); 137 | var stream = Fs.createReadStream(filename); 138 | 139 | stream.on('data', U.streamer('\n', function(chunk) { 140 | 141 | if (type) 142 | return false; 143 | 144 | chunk = chunk.toLowerCase(); 145 | 146 | for (var i = 0; i < length; i++) { 147 | if (chunk.indexOf(search[i]) !== -1) { 148 | type = search[i].startsWith('\n===') ? 'error' : search[i]; 149 | return false; 150 | } 151 | } 152 | 153 | })); 154 | 155 | CLEANUP(stream, function() { 156 | 157 | if (type) { 158 | item.analyzatoroutput !== type && EMIT('superadmin_app_analyzator', item); 159 | item.analyzatoroutput = type; 160 | 161 | var e = { id: item.id, type: type, url: item.url }; 162 | output.push(e); 163 | 164 | var publish = {}; 165 | publish.id = e.id; 166 | publish.name = item.name; 167 | publish.category = item.category; 168 | publish.type = e.type; 169 | publish.url = e.url; 170 | publish.dterror = NOW; 171 | publish.dttms = NOW; 172 | PUBLISH('apps_analyzator', e); 173 | 174 | } else 175 | item.analyzatoroutput = null; 176 | 177 | next(); 178 | }); 179 | 180 | }, () => $.callback(output), 2); 181 | }); 182 | 183 | // Reads logs 184 | schema.addWorkflow('logs', function($) { 185 | var item = APPLICATIONS.findItem('id', $.id); 186 | if (item) { 187 | Fs.readFile(Path.join(CONF.directory_www, item.linker, 'logs', 'debug.log'), function(err, response) { 188 | $.controller.plain(err ? '' : response.toString('utf8')); 189 | $.cancel(); 190 | }); 191 | } else 192 | $.invalid('404'); 193 | }); 194 | 195 | schema.addWorkflow('restart', function($) { 196 | 197 | var app = APPLICATIONS.findItem('id', $.id); 198 | if (!app) { 199 | $.invalid('404'); 200 | return; 201 | } 202 | 203 | SuperAdmin.logger('restart: {0}', $, app); 204 | EMIT('superadmin_app_restart', app); 205 | 206 | PUBLISH('apps_restart', FUNC.tms($, app)); 207 | 208 | reset_alarms(app.id); 209 | 210 | app.current = null; 211 | app.analyzatoroutput = null; 212 | 213 | if (app.stopped) { 214 | 215 | app.stopped = false; 216 | SuperAdmin.save(); 217 | 218 | if (app.url.startsWith('https://')) { 219 | 220 | // NGINX check due to SSL 221 | TASK('nginx/init', $.successful(function() { 222 | 223 | app.current = null; 224 | app.analyzatoroutput = null; 225 | 226 | SuperAdmin.wsnotify('app_restart', app); 227 | SuperAdmin.restart(app.port, $.successful(function() { 228 | SuperAdmin.pid2(app, function(err, pid) { 229 | pid && SuperAdmin.appinfo(pid, NOOP, app); 230 | }); 231 | })); 232 | 233 | $.success(); 234 | 235 | }), $).value = app; 236 | 237 | return; 238 | } 239 | } 240 | 241 | SuperAdmin.wsnotify('app_restart', app); 242 | SuperAdmin.restart(app.port, $.successful(function() { 243 | SuperAdmin.pid2(app, function(err, pid) { 244 | pid && SuperAdmin.appinfo(pid, NOOP, app); 245 | }); 246 | })); 247 | 248 | $.success(); 249 | 250 | }); 251 | 252 | schema.addWorkflow('restart_all', function($) { 253 | 254 | SuperAdmin.wsnotify('apps_restart'); 255 | MAIN.restarting = true; 256 | 257 | PUBLISH('apps_restart_all', FUNC.tms($, {})); 258 | 259 | APPLICATIONS.wait(function(app, next) { 260 | 261 | if (app.stopped || ($.query.version && app.version !== $.query.version)) { 262 | next(); 263 | return; 264 | } 265 | 266 | reset_alarms(app.id); 267 | 268 | app.current = null; 269 | app.analyzatoroutput = null; 270 | SuperAdmin.wsnotify('app_restart', app); 271 | SuperAdmin.restart(app.port, () => next()); 272 | 273 | PUBLISH('apps_restart', FUNC.tms($, app)); 274 | 275 | }, function() { 276 | 277 | MAIN.restarting = false; 278 | 279 | // Obtain apps info 280 | $WORKFLOW('apps', 'info', NOOP); 281 | 282 | }); 283 | 284 | $.success(); 285 | 286 | }); 287 | 288 | // Checks url 289 | schema.addWorkflow('check', function($, model) { 290 | 291 | var url = model.url.superadmin_clean(); 292 | 293 | model.path = model.url.substring(url.length); 294 | 295 | if (model.path === '/') 296 | model.path = ''; 297 | 298 | model.url = url; 299 | model.subprocess = !!model.path; 300 | 301 | if (model.servicemode) { 302 | $.success(); 303 | return; 304 | } 305 | 306 | if (!model.url) { 307 | $.invalid('url'); 308 | return; 309 | } 310 | 311 | var item; 312 | 313 | if (model.subprocess) { 314 | item = APPLICATIONS.findItem(n => n.url === model.url && !n.subprocess); 315 | if (item) 316 | $.success(); 317 | else 318 | $.invalid('404'); 319 | } else { 320 | item = APPLICATIONS.findItem('url', model.url); 321 | if (item && item.id !== model.id) 322 | $.invalid('error-url-exists'); 323 | else 324 | $.success(); 325 | } 326 | }); 327 | 328 | // Checks port number 329 | schema.addWorkflow('port', function($, model) { 330 | if (model.port) { 331 | if (port_check(APPLICATIONS, model.id, model.port)) { 332 | $.invalid('error-port'); 333 | return; 334 | } 335 | } else 336 | model.port = port_create(APPLICATIONS); 337 | $.success(); 338 | }); 339 | 340 | schema.setSave(function($, model) { 341 | 342 | var item = CLONE(model); 343 | var newbie = !model.id; 344 | 345 | if (!model.servicemode) { 346 | item.linker = model.linker = item.url.superadmin_linker(model.path); 347 | if (!item.linker) { 348 | $.invalid('url'); 349 | return; 350 | } 351 | } 352 | 353 | if (item.version === 'total3') 354 | item.threads = ''; 355 | 356 | item.restart = undefined; 357 | item.ssl = model.ssl = item.url.startsWith('https://'); 358 | 359 | if ((item.unixsocket && item.version !== 'total4') || !CONF.unixsocket) 360 | item.unixsocket = false; 361 | 362 | if (item.id) { 363 | 364 | var index = APPLICATIONS.findIndex('id', model.id); 365 | 366 | if (index === -1) { 367 | $.invalid('404'); 368 | return; 369 | } 370 | 371 | var app = APPLICATIONS[index]; 372 | 373 | if (model.servicemode) { 374 | item.url = app.url; 375 | item.linker = model.linker = item.url.superadmin_linker(model.path); 376 | } else { 377 | if (app.linker !== model.linker) { 378 | $.invalid('error-app-linker'); 379 | return; 380 | } 381 | } 382 | 383 | model.restart = app.cluster !== model.cluster || model.debug !== app.debug || model.version !== app.version || model.unixsocket !== app.unixsocket || model.editcode !== app.editcode; 384 | item.current = app.current; 385 | item.analyzatoroutput = app.analyzatoroutput; 386 | item.dtupdated = NOW; 387 | 388 | PUBLISH('apps_update', FUNC.tms($, item)); 389 | 390 | } else { 391 | 392 | item.id = model.id = UID(); 393 | item.dtcreated = NOW; 394 | model.restart = true; 395 | 396 | if (item.servicemode) { 397 | item.url = 'service-' + item.name.slug() + '-' + Date.now().toString(36); 398 | item.linker = model.linker = item.url.superadmin_linker(); 399 | } 400 | 401 | PUBLISH('apps_insert', FUNC.tms($, item)); 402 | 403 | } 404 | 405 | reset_alarms(item.id); 406 | 407 | var callback = function() { 408 | 409 | if (newbie) { 410 | SuperAdmin.wsnotify('app_create', item); 411 | APPLICATIONS.push(item); 412 | EMIT('superadmin_app_create', item); 413 | } else { 414 | SuperAdmin.wsnotify('app_update', item); 415 | APPLICATIONS[index] = item; 416 | EMIT('superadmin_app_update', item, index); 417 | } 418 | 419 | item.current = null; 420 | item.analyzatoroutput = null; 421 | 422 | model.restart && SuperAdmin.restart(item.port, $.successful(function() { 423 | SuperAdmin.pid2(item, function(err, pid) { 424 | pid && SuperAdmin.appinfo(pid, NOOP, item); 425 | }); 426 | })); 427 | 428 | SuperAdmin.save(null, true); 429 | $.success(item.id); 430 | 431 | MAIN.ws.send({ TYPE: 'refresh' }); 432 | }; 433 | 434 | if (item.servicemode) 435 | callback(); 436 | else 437 | TASK('nginx/init', $.successful(callback), $).value = item; 438 | 439 | }); 440 | 441 | function removeapp($, id, callback) { 442 | 443 | var index = APPLICATIONS.findIndex('id', id); 444 | if (index === -1) { 445 | callback('404'); 446 | return; 447 | } 448 | 449 | var app = APPLICATIONS[index]; 450 | 451 | PUBLISH('apps_remove', FUNC.tms($, app)); 452 | 453 | SuperAdmin.kill(app.port, function() { 454 | 455 | var linker = app.linker; 456 | var directory = Path.join(CONF.directory_www, linker); 457 | 458 | // Backups application's data 459 | Exec('bash {0} {1} {2}'.format(PATH.private('backup.sh'), Path.join(CONF.directory_www, linker), Path.join(CONF.directory_dump, linker + '-removed-backup.tar.gz')), function() { 460 | Exec('rm -r ' + directory, function() { 461 | 462 | EMIT('superadmin_app_remove', app); 463 | 464 | APPLICATIONS.splice(index, 1); 465 | SuperAdmin.save(null, true); 466 | 467 | var remove = []; 468 | 469 | if (!app.subprocess) { 470 | // remove all subprocesses 471 | for (var sub of APPLICATIONS) { 472 | if (sub.subprocess && sub.url === app.url) 473 | remove.push(sub); 474 | } 475 | } 476 | 477 | // Removes app directory 478 | Exec('rm ' + directory, NOOP); 479 | 480 | // Removes nginx config 481 | if (app.subprocess) { 482 | 483 | var parent = APPLICATIONS.findItem(n => n.url === app.url && !n.subprocess); 484 | if (parent) 485 | TASK('nginx/init', ERROR('Remove.nginx'), $).value = parent; 486 | 487 | } else 488 | PATH.unlink([Path.join(CONF.directory_nginx, linker + '.conf')], NOOP); 489 | 490 | if (remove.length) { 491 | remove.wait(function(item, next) { 492 | removeapp($, item.id, next); 493 | }, callback); 494 | } else { 495 | MAIN.ws.send({ TYPE: 'refresh' }); 496 | callback(); 497 | } 498 | }); 499 | }); 500 | }); 501 | } 502 | 503 | schema.setRemove(function($) { 504 | removeapp($, $.id, $.done()); 505 | }); 506 | 507 | schema.addWorkflow('stop', function($) { 508 | 509 | var app = APPLICATIONS.findItem('id', $.id); 510 | if (!app) { 511 | $.invalid('404'); 512 | return; 513 | } 514 | 515 | SuperAdmin.wsnotify('app_stop', app); 516 | SuperAdmin.logger('stop: {0}', $, app); 517 | SuperAdmin.kill(app.port, $.done()); 518 | 519 | PUBLISH('apps_stop', FUNC.tms($, app)); 520 | 521 | if (!app.stopped) { 522 | app.stopped = true; 523 | app.current = null; 524 | SuperAdmin.save(null, true); 525 | } 526 | 527 | if (MAIN.ws) { 528 | var current = {}; 529 | current.id = app.id; 530 | current.TYPE = 'appinfo'; 531 | current.is = false; 532 | current.analyzator = null; 533 | MAIN.ws.send(current); 534 | } 535 | 536 | }); 537 | 538 | schema.addWorkflow('stop_all', function($) { 539 | 540 | SuperAdmin.wsnotify('apps_stop'); 541 | SuperAdmin.logger('stops: all', $); 542 | 543 | PUBLISH('apps_stop_all', FUNC.tms($, {})); 544 | 545 | APPLICATIONS.wait(function(item, next) { 546 | 547 | if (item.stopped) { 548 | next(); 549 | return; 550 | } 551 | 552 | item.stopped = true; 553 | item.current = null; 554 | 555 | PUBLISH('apps_stop', FUNC.tms($, item)); 556 | 557 | SuperAdmin.kill(item.port, function() { 558 | if (MAIN.ws) { 559 | var current = {}; 560 | current.id = item.id; 561 | current.TYPE = 'appinfo'; 562 | current.analyzator = null; 563 | MAIN.ws.send(current); 564 | } 565 | next(); 566 | }); 567 | 568 | }, function() { 569 | SuperAdmin.save(null, true); 570 | $.success(); 571 | }); 572 | 573 | }); 574 | 575 | 576 | }); 577 | 578 | function port_create(arr) { 579 | var max = 7999; 580 | for (var i = 0; i < arr.length; i++) { 581 | var item = arr[i]; 582 | if (item.port > max) 583 | max = item.port; 584 | } 585 | return max + 1; 586 | } 587 | 588 | function port_check(arr, id, number) { 589 | var item = arr.findItem('port', number); 590 | return item ? item.id !== id : false; 591 | } 592 | -------------------------------------------------------------------------------- /schemas/filebrowser.js: -------------------------------------------------------------------------------- 1 | const Fs = require('fs'); 2 | const Path = require('path'); 3 | const ALLOWED = ['/config', '/config-debug', '/config-release', '/dependencies', '/workflows', '/versions', '/sitemap']; 4 | const SKIP = /\/(tmp|\.git)\//; 5 | 6 | NEWSCHEMA('FileBrowser', function(schema) { 7 | 8 | schema.define('filename', 'String(100)', true); 9 | schema.define('type', ['create', 'remove', 'update', 'rename'], true); 10 | schema.define('directory', Boolean); 11 | schema.define('body', String); 12 | schema.trim = false; 13 | 14 | schema.setQuery(function($) { 15 | 16 | var app = APPLICATIONS.findItem('id', $.id); 17 | if (!app) { 18 | $.invalid('404'); 19 | return; 20 | } 21 | 22 | var path = CONF.directory_www + app.linker + '/'; 23 | // SuperAdmin.logger('filebrowser.open: ' + path, $.controller, app); 24 | 25 | U.ls(path, function(files, directories) { 26 | 27 | for (var i = 0; i < files.length; i++) 28 | files[i] = files[i].substring(path.length - 1); 29 | 30 | for (var i = 0; i < directories.length; i++) 31 | directories[i] = directories[i].substring(path.length - 1); 32 | 33 | $.callback({ files: files, directories: directories, url: app.url, linker: app.linker, id: app.id }); 34 | 35 | }, n => !SKIP.test(n)); 36 | }); 37 | 38 | schema.setRead(function($) { 39 | 40 | var app = APPLICATIONS.findItem('id', $.id); 41 | 42 | if (!app) { 43 | $.invalid('404'); 44 | return; 45 | } 46 | 47 | var filename = $.query.filename; 48 | var ext = U.getExtension(filename); 49 | 50 | switch (ext) { 51 | case 'cache': 52 | case 'css': 53 | case 'htm': 54 | case 'html': 55 | case 'api': 56 | case 'js': 57 | case 'json': 58 | case 'log': 59 | case 'md': 60 | case 'nosql': 61 | case 'table': 62 | case 'gitignore': 63 | case 'npmignore': 64 | case 'bundle': 65 | case 'package': 66 | case 'bundlesignore': 67 | case 'nosql-log': 68 | case 'nosql-counter': 69 | case 'nosql-backup': 70 | case 'nosql-counter2': 71 | case 'nosql-mapreduce': 72 | case 'meta': 73 | case 'counter': 74 | case 'table-meta': 75 | case 'table-log': 76 | case 'table-counter': 77 | case 'table-counter2': 78 | case 'resource': 79 | case 'svg': 80 | case 'txt': 81 | case 'sql': 82 | case 'sh': 83 | break; 84 | default: 85 | 86 | if (filename.substring(0, 6) === '/.src/') 87 | filename = filename.substring(5); 88 | 89 | if (ALLOWED.indexOf(filename) === -1) { 90 | $.invalid('error-file-invalid'); 91 | return; 92 | } 93 | 94 | break; 95 | } 96 | 97 | var path = Path.join(CONF.directory_www + app.linker, $.query.filename); 98 | 99 | SuperAdmin.logger('filebrowser.file.read: ' + path, $.controller, app); 100 | 101 | Fs.readFile(path, function(err, response) { 102 | if (err) 103 | $.invalid(err); 104 | else { 105 | $.controller.binary(response, 'text/plain'); 106 | $.cancel(); 107 | } 108 | }); 109 | }); 110 | 111 | schema.setSave(function($, model) { 112 | 113 | var app = APPLICATIONS.findItem('id', $.id); 114 | if (!app) { 115 | $.invalid('404'); 116 | return; 117 | } 118 | 119 | var path = Path.join(CONF.directory_www + app.linker, model.filename); 120 | 121 | if (model.type === 'create') { 122 | var mkdir = path; 123 | if (!model.directory) 124 | mkdir = mkdir.substring(0, mkdir.lastIndexOf('/')); 125 | PATH.mkdir(mkdir); 126 | } 127 | 128 | if (model.directory) { 129 | 130 | if (model.type === 'create') { 131 | SuperAdmin.logger('filebrowser.directory.create: ' + path, $.controller, app); 132 | PATH.mkdir(path); 133 | $.success(); 134 | return; 135 | } else if (model.type === 'remove') { 136 | SuperAdmin.logger('filebrowser.directory.remove: ' + path, $.controller, app); 137 | PATH.rmdir(path, $.done()); 138 | return; 139 | } 140 | 141 | } else { 142 | 143 | if (model.type === 'create' || model.type === 'update') { 144 | SuperAdmin.logger('filebrowser.file.{0}: '.format(model.type) + path, $.controller, app); 145 | Fs.writeFile(path, model.body || '', $.done()); 146 | return; 147 | 148 | } else if (model.type === 'rename') { 149 | var target = Path.join(CONF.directory_www + app.linker, model.body); 150 | SuperAdmin.logger('filebrowser.file.rename: ' + path + ' to ' + target, $.controller, app); 151 | Fs.rename(path, target, $.done()); 152 | return; 153 | } else if (model.type === 'remove') { 154 | SuperAdmin.logger('filebrowser.file.remove: ' + path, $.controller, app); 155 | Fs.unlink(path, $.done()); 156 | return; 157 | } 158 | } 159 | 160 | }); 161 | 162 | schema.addWorkflow('upload', function($) { 163 | 164 | if (!$.query.path) { 165 | $.invalid('Invalid path'); 166 | return; 167 | } 168 | 169 | var app = APPLICATIONS.findItem('id', $.query.id || ''); 170 | if (!app) { 171 | $.invalid('404'); 172 | return; 173 | } 174 | 175 | var path = Path.join(CONF.directory_www + app.linker, $.query.path); 176 | var mkdir = path.substring(0, path.lastIndexOf('/')); 177 | 178 | PATH.mkdir(mkdir); 179 | SuperAdmin.logger('filebrowser.file.upload: ' + path, $.controller, app); 180 | $.files[0].move(path, $.done()); 181 | }); 182 | 183 | }); -------------------------------------------------------------------------------- /schemas/notifications.js: -------------------------------------------------------------------------------- 1 | NEWSCHEMA('Notifications', function(schema) { 2 | 3 | schema.setQuery(function($) { 4 | PREF.notifications && PREF.set('notifications', 0); 5 | NOSQL('notifications').find2().take(50).callback($.callback); 6 | }); 7 | 8 | schema.setRemove(function($) { 9 | NOSQL('notifications').clear().callback($.done()); 10 | }); 11 | 12 | }); -------------------------------------------------------------------------------- /schemas/operations.js: -------------------------------------------------------------------------------- 1 | const Exec = require('child_process').exec; 2 | 3 | NEWSCHEMA('Operations', function(schema) { 4 | 5 | schema.addWorkflow('backup', function($) { 6 | SuperAdmin.logger('backup: all', $); 7 | SuperAdmin.backup($.successful(function(filename) { 8 | $.controller.file('~' + filename, U.getName(filename)); 9 | $.cancel(); 10 | })); 11 | }); 12 | 13 | schema.addWorkflow('fixpermissions', function($) { 14 | Exec('chown -R {0}:{1} /www/www/*'.format(SuperAdmin.run_as_user.group, SuperAdmin.run_as_user.id), $.done()); 15 | }); 16 | 17 | schema.addWorkflow('updatetotal', function($) { 18 | Exec(PATH.private('update-total.sh'), $.done()); 19 | }); 20 | 21 | }); -------------------------------------------------------------------------------- /schemas/settings.js: -------------------------------------------------------------------------------- 1 | const Fs = require('fs'); 2 | 3 | NEWSCHEMA('Settings', function(schema) { 4 | 5 | schema.define('name', String, true); 6 | schema.define('emailsummarization', String); 7 | schema.define('totalapi', String); 8 | schema.define('allow_totalapi', Boolean); 9 | schema.define('allow_tms', Boolean); 10 | schema.define('sms_from', String); 11 | schema.define('secret_tms', String); 12 | schema.define('mail_api', Boolean); 13 | schema.define('mail_smtp', String); 14 | schema.define('mail_smtp_options', String); 15 | schema.define('mail_address_from', String); 16 | schema.define('allowbackup', Boolean); 17 | schema.define('ftp', String); 18 | schema.define('intervalbackup', Number); 19 | 20 | var save = function($, model) { 21 | LOADCONFIG(model); 22 | CMD('clear_smtpcache'); 23 | Fs.writeFile(PATH.databases('settings.json'), JSON.stringify(model), $.done()); 24 | SuperAdmin.logger('save: settings', $); 25 | EMIT('superadmin_settings', model); 26 | }; 27 | 28 | schema.setSave(function($, model) { 29 | 30 | if (!$.user.sa) { 31 | $.invalid('401'); 32 | return; 33 | } 34 | 35 | if (model.mail_smtp_options) 36 | model.mail_smtp_options = model.mail_smtp_options.parseJSON(); 37 | 38 | if (model.mail_smtp) { 39 | Mail.try(model.mail_smtp, model.mail_smtp_options, $.successful(function() { 40 | save($, model); 41 | })); 42 | } else 43 | save($, model); 44 | 45 | }); 46 | 47 | schema.setRead(function($) { 48 | 49 | if (!$.user.sa) { 50 | $.invalid('401'); 51 | return; 52 | } 53 | 54 | var data = {}; 55 | 56 | for (var key of schema.fields) { 57 | var value = CONF[key]; 58 | if (value && typeof(value) === 'object') 59 | value = JSON.stringify(value); 60 | data[key] = value; 61 | } 62 | 63 | $.callback(data); 64 | }); 65 | 66 | schema.addWorkflow('load', function($) { 67 | Fs.readFile(PATH.databases('settings.json'), function(err, response) { 68 | 69 | var data = response ? response.toString('utf8').parseJSON(true) : {}; 70 | var isempty = false; 71 | 72 | if (data.name == null) { 73 | data.name = CONF.name; 74 | isempty = true; 75 | } 76 | 77 | if (data.sms_from == null) 78 | data.sms_from = CONF.name; 79 | 80 | if (data.allow_tms == null) 81 | data.allow_tms = false; 82 | 83 | if (data.allow_totalapi == null) 84 | data.allow_totalapi = false; 85 | 86 | if (data.intervalbackup == null) 87 | data.intervalbackup = 6; 88 | 89 | if (data.allowbackup == null) 90 | data.allowbackup = false; 91 | 92 | if (!isempty) 93 | LOADCONFIG(data); 94 | 95 | $.success(); 96 | }); 97 | }); 98 | 99 | }); -------------------------------------------------------------------------------- /schemas/templates.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const Fs = require('fs'); 3 | const Spawn = require('child_process').spawn; 4 | const Exec = require('child_process').exec; 5 | 6 | NEWSCHEMA('Templates', function(schema) { 7 | 8 | schema.define('template', String, true); 9 | schema.define('remove', Boolean); 10 | schema.define('npm', Boolean); 11 | schema.define('backup', Boolean); 12 | 13 | schema.setQuery(function($) { 14 | RESTBuilder.GET('https://raw.githubusercontent.com/totaljs/superadmin_templates/main/superadmin9.json').callback($.callback); 15 | }); 16 | 17 | schema.addWorkflow('check', function($, model) { 18 | 19 | var app = APPLICATIONS.findItem('id', $.id); 20 | if (!app) { 21 | $.invalid('404'); 22 | return; 23 | } 24 | 25 | model.applinker = app.linker; 26 | model.appdirectory = Path.join(CONF.directory_www, model.applinker); 27 | model.app = app; 28 | model.filename = Path.join(CONF.directory_www, app.url.superadmin_linker(app.path), app.id + '.zip'); 29 | 30 | if (!model.template) { 31 | $.invalid('error-template'); 32 | return; 33 | } 34 | 35 | if (model.template === 'upload') { 36 | PATH.exists(model.filename, function(e) { 37 | if (e) 38 | $.success(); 39 | else 40 | $.invalid('filename'); 41 | }); 42 | return; 43 | } 44 | 45 | if (model.template === 'restore') { 46 | PATH.exists(CONF.directory_dump + model.app.id + '-backup.tar.gz', function(e) { 47 | if (e) 48 | $.success(); 49 | else 50 | $.invalid('error-restore'); 51 | }); 52 | return; 53 | } 54 | 55 | DOWNLOAD(model.template, model.filename, $.done()); 56 | }); 57 | 58 | schema.addWorkflow('stop', function($, model) { 59 | SuperAdmin.kill(model.app.port, function() { 60 | setTimeout($.done(), 1000); 61 | }); 62 | }); 63 | 64 | schema.addWorkflow('restart', function($, model) { 65 | 66 | if (model.app.stopped) { 67 | model.app.stopped = false; 68 | SuperAdmin.save(NOOP); 69 | } 70 | 71 | if (model.app.stats) { 72 | if (model.app.stats.restart) 73 | model.app.stats.restart++; 74 | else 75 | model.app.stats.restart = 1; 76 | } 77 | 78 | run(model.npm, model.app, $.done()); 79 | }); 80 | 81 | schema.addWorkflow('backup', function($, model) { 82 | if (model.backup) 83 | Exec('bash {0} {1} {2}'.format(PATH.private('backup.sh'), Path.join(CONF.directory_www, model.app.linker), Path.join(CONF.directory_dump, model.app.id + '-backup.tar.gz')), $.done()); 84 | else 85 | $.success(); 86 | }); 87 | 88 | schema.addWorkflow('remove', function($, model) { 89 | 90 | if (!model.remove) { 91 | $.success(); 92 | return; 93 | } 94 | 95 | U.ls(model.appdirectory, function(files, directories) { 96 | files = files.remove(n => n === model.filename); 97 | PATH.unlink(files, function() { 98 | directories.wait(function(item, next) { 99 | Fs.rmdir(item, () => next()); 100 | }, $.done()); 101 | }); 102 | }); 103 | }); 104 | 105 | schema.addWorkflow('unpack', function($, model) { 106 | 107 | var linker = model.app.linker; 108 | var directory = Path.join(CONF.directory_www, linker); 109 | 110 | // Restore from backup 111 | if (model.template === 'restore') { 112 | Exec('tar -xzvf {0} --directory {1} > /dev/null'.format(CONF.directory_dump + model.app.id + '-backup.tar.gz', directory), function() { 113 | SuperAdmin.wsnotify('app_template', model.app); 114 | Spawn('chown', ['-R', SuperAdmin.run_as_user.user, directory]); 115 | $.success(); 116 | }); 117 | } else { 118 | // Extract file 119 | Exec('unzip -o {0}.zip'.format(model.app.id), { cwd: directory }, function() { 120 | PATH.unlink('{0}/{1}.zip'.format(directory, model.app.id)); 121 | SuperAdmin.wsnotify('app_template', model.app); 122 | Spawn('chown', ['-R', SuperAdmin.run_as_user.user, directory]); 123 | $.success(); 124 | }); 125 | } 126 | 127 | }); 128 | }); 129 | 130 | function run(npm, model, callback) { 131 | 132 | if (npm) { 133 | return SuperAdmin.npminstall(model, function() { 134 | SuperAdmin.makescripts(model, function() { 135 | SuperAdmin.restart(model.port, function() { 136 | SuperAdmin.pid2(model, function(err, pid) { 137 | pid && SuperAdmin.appinfo(pid, NOOP, model); 138 | }); 139 | callback && callback(); 140 | }); 141 | }); 142 | }); 143 | } 144 | 145 | return SuperAdmin.makescripts(model, function() { 146 | SuperAdmin.restart(model.port, function() { 147 | SuperAdmin.pid2(model, function(err, pid) { 148 | pid && SuperAdmin.appinfo(pid, NOOP, model); 149 | }); 150 | callback && callback(); 151 | }); 152 | }); 153 | } 154 | -------------------------------------------------------------------------------- /schemas/users.js: -------------------------------------------------------------------------------- 1 | NEWSCHEMA('Users', function(schema) { 2 | 3 | schema.define('name', String, true); 4 | schema.define('login', String, true); 5 | schema.define('password', String); 6 | schema.define('sa', Boolean); 7 | schema.define('isdisabled', Boolean); 8 | 9 | schema.setQuery(function($) { 10 | if ($.user.sa) 11 | NOSQL('users').find().fields('id,name,login,isdisabled,dtcreated,dtlogged,sa,isonline').sort('dtcreated_asc').callback($.callback); 12 | else 13 | $.invalid('401'); 14 | }); 15 | 16 | schema.setRead(function($) { 17 | if ($.user.sa) 18 | NOSQL('users').read().fields('id,name,login,isdisabled,dtcreated,dtlogged,sa').id($.id).error('404').callback($.callback); 19 | else 20 | $.invalid('401'); 21 | }); 22 | 23 | schema.setInsert(function($, model) { 24 | 25 | if (!$.user.sa) { 26 | $.invalid('401'); 27 | return; 28 | } 29 | 30 | model.id = UID(); 31 | model.dtcreated = NOW; 32 | model.password = model.password.sha256(CONF.session_secret); 33 | NOSQL('users').insert(model).callback($.done()); 34 | }); 35 | 36 | schema.setUpdate(function($, model) { 37 | 38 | if (!$.user.sa) { 39 | $.invalid('401'); 40 | return; 41 | } 42 | 43 | model.dtupdated = NOW; 44 | model.password = model.password ? model.password.sha256(CONF.session_secret) : undefined; 45 | NOSQL('users').modify(model).id($.id).callback($.done()); 46 | 47 | }); 48 | 49 | schema.setRemove(function($) { 50 | 51 | if (!$.user.sa || $.id === $.user.id) { 52 | $.invalid('401'); 53 | return; 54 | } 55 | 56 | NOSQL('sessions').remove().where('userid', $.id); 57 | NOSQL('users').remove().id($.id).callback($.done()); 58 | }); 59 | 60 | NOSQL('users').read().callback(function(err, response) { 61 | // tries to find a user 62 | if (!response) { 63 | var password = GUID(10); 64 | var model = {}; 65 | model.id = UID(); 66 | model.dtcreated = NOW; 67 | model.name = 'Total Admin'; 68 | model.login = GUID(10); 69 | model.sa = true; 70 | model.isdisabled = false; 71 | model.password = password.sha256(CONF.session_secret); 72 | NOSQL('users').insert(model); 73 | PREF.set('credentials', { login: model.login, password: password }); 74 | } 75 | }); 76 | 77 | }); -------------------------------------------------------------------------------- /selfsigned.sh: -------------------------------------------------------------------------------- 1 | name="/www/ssl/superadmin" 2 | commonname=superadmin 3 | country=XX 4 | state=Earth 5 | locality=World 6 | organization=Total 7 | organizationalunit=IT 8 | email=support@totaljs.com 9 | 10 | sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout $name.key -out $name.csr -subj "/C=$country/ST=$state/L=$locality/O=$organization/OU=$organizationalunit/CN=$commonname/emailAddress=$email" -------------------------------------------------------------------------------- /ssl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "$@" == *"-renew"* ]] 4 | then 5 | /root/.acme.sh/acme.sh --certhome /www/ssl --issue -d $1 --renew --force -w /www/acme --keylength 2048 6 | else 7 | if [[ "$@" == *"-update"* ]] 8 | then 9 | /root/.acme.sh/acme.sh upgrade 10 | else 11 | /root/.acme.sh/acme.sh --certhome /www/ssl --issue -d $1 -w /www/acme --keylength 2048 12 | fi 13 | fi -------------------------------------------------------------------------------- /superadmin.conf: -------------------------------------------------------------------------------- 1 | #disablehttps#server{ 2 | #disablehttps# listen 80; 3 | #disablehttps# server_name #domain#; 4 | #disablehttps# location ^~ /.well-known/acme-challenge/ { 5 | #disablehttps# default_type "text/plain"; 6 | #disablehttps# root /www/acme/; 7 | #disablehttps# break; 8 | #disablehttps# } 9 | #disablehttps# location ~ / { 10 | #disablehttps# return 301 https://#domain#$request_uri; 11 | #disablehttps# } 12 | #disablehttps#} 13 | 14 | server { 15 | 16 | #disablehttp# listen 80; 17 | #disablehttps# listen 443 http2 ssl; 18 | server_name #domain#; 19 | charset utf-8; 20 | 21 | #disablehttps# ssl_certificate /www/ssl/#domain#/fullchain.cer; 22 | #disablehttps# ssl_certificate_key /www/ssl/#domain#/#domain#.key; 23 | #disablehttps# ssl_trusted_certificate /www/ssl/#domain#/fullchain.cer; 24 | #disablehttps# ssl_session_timeout 1d; 25 | #disablehttps# ssl_session_cache shared:SSL:10m; 26 | #disablehttps# ssl_session_tickets off; 27 | #disablehttps# ssl_protocols TLSv1.2 TLSv1.3; 28 | #disablehttps# ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 29 | #disablehttps# ssl_prefer_server_ciphers off; 30 | #disablehttps# ssl_stapling on; 31 | #disablehttps# ssl_stapling_verify on; 32 | #disablehttps# add_header Strict-Transport-Security "max-age=63072000" always; 33 | 34 | client_max_body_size 50M; 35 | 36 | #disablehttp# location ^~ /.well-known/acme-challenge/ { 37 | #disablehttp# default_type "text/plain"; 38 | #disablehttp# root /www/acme/; 39 | #disablehttp# break; 40 | #disablehttp# } 41 | 42 | location / { 43 | limit_req zone=ddos burst=33 nodelay; 44 | proxy_set_header Host $http_host; 45 | proxy_set_header X-Forwarded-For $remote_addr; 46 | proxy_set_header X-Forwarded-Protocol $scheme; 47 | proxy_set_header X-NginX-Proxy true; 48 | proxy_set_header Upgrade $http_upgrade; 49 | proxy_set_header Connection "upgrade"; 50 | proxy_redirect off; 51 | proxy_http_version 1.1; 52 | proxy_buffering off; 53 | proxy_cache_bypass $http_upgrade; 54 | proxy_cache_key sfs$request_uri$scheme; 55 | proxy_pass_header X-Ping; 56 | proxy_read_timeout 600s; 57 | proxy_pass http://127.0.0.1:9999; 58 | break; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tasks/nginx.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const Fs = require('fs'); 3 | const Exec = require('child_process').exec; 4 | 5 | NEWTASK('nginx', function(push) { 6 | 7 | // $.value {String} Application ID or Application instance 8 | 9 | push('init', function($) { 10 | 11 | var app = typeof($.value) === 'object' ? $.value : null; 12 | var value = $.value = { id: $.value }; 13 | 14 | // APP ID 15 | if (!app) 16 | app = APPLICATIONS.findItem('id', $.value.id); 17 | 18 | if (app) { 19 | 20 | if (app.subprocess) { 21 | 22 | app = APPLICATIONS.findItem(n => n.url === app.url && !n.subprocess); 23 | 24 | if (!app) { 25 | $.invalid('@(Master process not found)'); 26 | return; 27 | } 28 | 29 | value = $.value = { id: app.id }; 30 | } 31 | 32 | value.filename = Path.join(CONF.directory_nginx, app.linker + '.conf'); 33 | 34 | var model = value.model = {}; 35 | var domains = []; 36 | 37 | model.domain = app.url.superadmin_url(); 38 | app.domains = model.domain.superadmin_domains(); 39 | app.ssl = app.url.startsWith('https://'); 40 | 41 | for (var i = 0; i < app.domains.length; i++) 42 | domains.push({ domain: app.domains[i], ssl_cer: CONF.directory_ssl + 'superadmin.csr', ssl_key: CONF.directory_ssl + 'superadmin.key' }); 43 | 44 | if (app.redirect) { 45 | for (var i = 0; i < app.redirect.length; i++) { 46 | var d = app.redirect[i]; 47 | if (!domains.findItem('domain', d)) 48 | domains.push({ domain: d, ssl_cer: CONF.directory_ssl + 'superadmin.csr', ssl_key: CONF.directory_ssl + 'superadmin.key' }); 49 | } 50 | } 51 | 52 | model.childs = []; 53 | 54 | // sub processes 55 | for (var sub of APPLICATIONS) { 56 | if (sub.subprocess && sub.url === app.url) { 57 | 58 | var mod = {}; 59 | mod.endpoint = sub.path; 60 | mod.islogging = sub.accesslog; 61 | mod.allowedip = sub.allow; 62 | mod.blockedip = sub.disallow; 63 | mod.uploadquote = sub.size; 64 | mod.ddosquote = sub.ddosquote; 65 | mod.proxytimeout = sub.proxytimeout; 66 | mod.port = sub.port; 67 | mod.threads = sub.threads; 68 | 69 | if (CONF.unixsocket && sub.unixsocket) 70 | mod.unixsocket = Path.join(CONF.directory_www, sub.linker, 'superadmin.socket'); 71 | 72 | model.childs.push(mod); 73 | } 74 | } 75 | 76 | model.allow80 = app.allow80; 77 | model.isssl = app.ssl; 78 | model.domains = domains; 79 | model.acmethumbprint = SuperAdmin.options.acmethumbprint; 80 | model.linker = app.linker; 81 | model.islogging = app.accesslog; 82 | model.allowedip = app.allow; 83 | model.blockedip = app.disallow; 84 | model.uploadquote = app.size; 85 | model.ddosquote = app.ddosquote; 86 | model.proxytimeout = app.proxytimeout; 87 | model.port = app.port; 88 | model.threads = app.threads; 89 | 90 | if (CONF.unixsocket && app.unixsocket) 91 | model.unixsocket = Path.join(CONF.directory_www, app.linker, 'superadmin.socket'); 92 | 93 | // Custom keys 94 | if (app.ssl) { 95 | 96 | if (app.ssl_cer && app.ssl_key) { 97 | model.ssl_cer = app.ssl_cer; 98 | model.ssl_key = app.ssl_key; 99 | } else { 100 | // Default self-signed certificate 101 | model.ssl_cer = CONF.directory_ssl + 'superadmin.csr'; 102 | model.ssl_key = CONF.directory_ssl + 'superadmin.key'; 103 | model.ssl_generate = true; 104 | } 105 | 106 | if (domains.length > 1) 107 | model.redirectssl = domains[1]; 108 | 109 | // http2https redirect 110 | model.redirect = domains; 111 | 112 | } else { 113 | 114 | // http2http redirect 115 | if (domains.length > 1) 116 | model.redirect = [domains[1]]; 117 | } 118 | 119 | if (app.allow80 && app.url.startsWith('https:')) 120 | model.redirect = model.redirect.slice(1); 121 | 122 | $.next('create'); 123 | 124 | } else 125 | $.invalid('404'); 126 | }); 127 | 128 | // Creates NGINX configuration 129 | push('create', function($, value) { 130 | Fs.readFile(PATH.private('nginx.conf'), function(err, response) { 131 | response = response.toString('utf8'); 132 | Fs.writeFile(value.filename, VIEWCOMPILE(response, value.model).trim().replace(/\n\t\n/g, '\n').replace(/\n{3,}/g, '\n'), $.next2('test')); 133 | }); 134 | }); 135 | 136 | // Tests NGINX configuration 137 | push('test', function($, value) { 138 | Exec(SuperAdmin.options.nginxpath + ' -t', function(err) { 139 | if (err) { 140 | // ERROR 141 | // We need to remove the config 142 | Fs.unlink(value.filename, NOOP); 143 | $.invalid(err); 144 | } else { 145 | Exec(SuperAdmin.options.nginxpath + ' -s reload', function(err, response, output) { 146 | if (err) { 147 | // Unhandled problem 148 | // Maybe remove a config file? 149 | $.invalid(err); 150 | } else if (value.model.isssl && !value.isssl && value.model.ssl_generate) { 151 | value.isssl = true; 152 | $.next('ssl'); 153 | } else 154 | $.success(true, output); 155 | }); 156 | } 157 | }); 158 | }); 159 | 160 | push('ssl', function($, value) { 161 | var domains = value.model.domains; 162 | domains.wait(function(item, next) { 163 | TASK('ssl/init', function(err, response) { 164 | if (response.success) { 165 | item.ssl_cer = response.value.ssl_cer; 166 | item.ssl_key = response.value.ssl_key; 167 | if (item.domain === value.model.domain) { 168 | value.model.ssl_cer = item.ssl_cer; 169 | value.model.ssl_key = item.ssl_key; 170 | } 171 | } 172 | next(); 173 | }, $).value = item.domain; 174 | }, $.next2('create')); 175 | }); 176 | 177 | }); -------------------------------------------------------------------------------- /tasks/ssl.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const Fs = require('fs'); 3 | const Exec = require('child_process').exec; 4 | 5 | NEWTASK('ssl', function(push) { 6 | 7 | // $.value {String} a domain name 8 | 9 | push('init', function($, value) { 10 | var filename = Path.join(CONF.directory_ssl, value, value + '.cer'); 11 | Fs.lstat(filename, err => $.next(err ? 'create' : 'check')); 12 | }); 13 | 14 | // Checks validity 15 | push('check', function($, value) { 16 | Exec('cat {0} | openssl x509 -noout -enddate'.format(Path.join(CONF.directory_ssl, value, value + '.cer')), function(err, response) { 17 | 18 | if (err) { 19 | // Certificate not found, we make it 20 | $.next('create'); 21 | return; 22 | } 23 | 24 | var index = response.indexOf('='); 25 | if (index !== -1) { 26 | var expire = new Date(Date.parse(response.substring(index + 1).trim())); 27 | 28 | // Certificate will expire less than 10 days 29 | if (expire.diff('days') < 10) 30 | $.next('renew'); 31 | else 32 | $.next('done'); 33 | } 34 | 35 | }); 36 | }); 37 | 38 | push('renew', function($, value) { 39 | Exec(SuperAdmin.options.acmepath + ' --certhome {0} --{3} -d {1} -w {2} --stateless --keylength 2048'.format(CONF.directory_ssl, value, CONF.directory_acme, 'renew --force'), function(err) { 40 | 41 | SuperAdmin.send_notify(err ? 'warning' : 'success', err ? TRANSLATOR('', '@(A problem with renewing of SSL certificate for domain {0}. Error: {1})').format(value, err.message) : TRANSLATOR('', '@(SSL certificate has been renewed successfully for {0})').format(value)); 42 | 43 | if (err) 44 | $.invalid('error-ssl-renew', value + ': ' + err); 45 | else 46 | $.next('done'); 47 | }); 48 | }); 49 | 50 | push('create', function($, value) { 51 | Exec(SuperAdmin.options.acmepath + ' --certhome {0} --{3} -d {1} -w {2} --stateless --keylength 2048'.format(CONF.directory_ssl, value, CONF.directory_acme, 'issue --force'), function(err) { 52 | if (err) 53 | $.invalid('error-ssl-create', value + ': ' + err); 54 | else 55 | $.next('done'); 56 | }); 57 | }); 58 | 59 | push('done', function($, value) { 60 | var obj = {}; 61 | obj.domain = value; 62 | obj.ssl_cer = CONF.directory_ssl + value + '/fullchain.cer'; 63 | obj.ssl_key = CONF.directory_ssl + value + '/' + value + '.key'; 64 | $.success(obj); 65 | }); 66 | 67 | }); -------------------------------------------------------------------------------- /update-centos.sh: -------------------------------------------------------------------------------- 1 | cd /www/ 2 | 3 | echo "Install missing Linux packages" 4 | 5 | yum install -y -q sysstat 6 | yum install -y -q procps-ng 7 | 8 | echo "Install NPM dependencies" 9 | npm install total4 2>/dev/null 10 | npm install dbms 2>/dev/null 11 | 12 | echo "Backing up old SuperAdmin: /www/superadmin_bk.zip" 13 | zip -r superadmin_bk.zip superadmin 2>/dev/null 14 | 15 | echo "Kills all running apps" 16 | pkill -f total 17 | 18 | mkdir superadmin_tmp 19 | cp /www/superadmin/databases/applications.json /www/superadmin_tmp/applications.json 20 | cp /www/superadmin/databases/stats.nosql /www/superadmin_tmp/stats.nosql 21 | cp /www/superadmin/databases/acmethumbprint.txt /www/superadmin_tmp/acmethumbprint.txt 22 | 23 | SA_PID=$(lsof -i :9999 | grep "LISTEN" | awk {'print $2'}) 24 | 25 | if [[ $SA_PID ]] 26 | then 27 | echo "Killing old instance of SuperAdmin" 28 | kill -9 $SA_PID 29 | fi 30 | 31 | rm -rf /www/superadmin/ 32 | mkdir -p /www/superadmin/logs/ 33 | 34 | cd /www/superadmin/ 35 | echo "Downloading of new version of SuperAdmin" 36 | wget "https://raw.githubusercontent.com/totaljs/superadmin_templates/main/superadmin.zip" 2>/dev/null 37 | unzip superadmin.zip 38 | rm superadmin.zip 39 | 40 | mkdir databases 41 | cp /www/superadmin_tmp/applications.json /www/superadmin/databases/applications.json 42 | cp /www/superadmin_tmp/stats.nosql /www/superadmin/databases/stats.nosql 43 | cp /www/superadmin_tmp/acmethumbprint.txt /www/superadmin/databases/acmethumbprint.txt 44 | 45 | echo "Running..." 46 | bash run.sh 47 | 48 | echo "Done!" -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | cd /www/ 2 | 3 | echo "Install missing Linux packages" 4 | 5 | apt-get install -y sysstat 6 | apt-get install -y procps 7 | 8 | echo "Install NPM dependencies" 9 | npm install total4 2>/dev/null 10 | npm install dbms 2>/dev/null 11 | npm install pg 2>/dev/null 12 | 13 | echo "Backing up old SuperAdmin: /www/superadmin_bk.zip" 14 | zip -r superadmin_bk.zip superadmin 2>/dev/null 15 | 16 | echo "Kills all running apps" 17 | pkill -f total 18 | 19 | mkdir superadmin_tmp 20 | cp /www/superadmin/databases/applications.json /www/superadmin_tmp/applications.json 21 | cp /www/superadmin/databases/stats.nosql /www/superadmin_tmp/stats.nosql 22 | cp /www/superadmin/databases/acmethumbprint.txt /www/superadmin_tmp/acmethumbprint.txt 23 | 24 | SA_PID=$(lsof -i :9999 | grep "LISTEN" | awk {'print $2'}) 25 | 26 | if [[ $SA_PID ]] 27 | then 28 | echo "Killing old instance of SuperAdmin" 29 | kill -9 $SA_PID 30 | fi 31 | 32 | rm -rf /www/superadmin/ 33 | mkdir -p /www/superadmin/logs/ 34 | 35 | cd /www/superadmin/ 36 | echo "Downloading of new version of SuperAdmin" 37 | wget "https://raw.githubusercontent.com/totaljs/superadmin_templates/main/superadmin.zip" 2>/dev/null 38 | unzip superadmin.zip 39 | rm superadmin.zip 40 | 41 | cp /www/superadmin/nginx.conf /etc/nginx/nginx.conf 42 | cp /www/superadmin/ffdhe2048.pem /etc/nginx/ffdhe2048.pem 43 | 44 | mkdir databases 45 | cp /www/superadmin_tmp/applications.json /www/superadmin/databases/applications.json 46 | cp /www/superadmin_tmp/stats.nosql /www/superadmin/databases/stats.nosql 47 | cp /www/superadmin_tmp/acmethumbprint.txt /www/superadmin/databases/acmethumbprint.txt 48 | 49 | echo "Running..." 50 | bash run.sh 51 | 52 | echo "Done!" -------------------------------------------------------------------------------- /user.guid: -------------------------------------------------------------------------------- 1 | root:0:0 2 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | @{layout('')} 2 | 3 | 4 | 5 | 6 | @{config.name} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | @{import('head', 'ui.css + default.css', 'ui.js + helpers.js + syntax.js', 'favicon.ico')} 19 | 20 | 21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 38 |
39 | 44 |
45 | 46 |
47 |
48 |
49 | 50 | 51 | 52 | 122 | 123 |
124 | 125 | @{json(user, 'userdata')} 126 | 127 | 370 | 371 | 372 | 373 | -------------------------------------------------------------------------------- /views/login.html: -------------------------------------------------------------------------------- 1 | @{layout('')} 2 | 3 | 4 | 5 | 6 | @{config.name} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 57 | @{import('ui.css', 'ui.js')} 58 | 59 | 60 | 61 |
62 | 63 |
64 |
65 |

@{'%name'}

66 |
67 | 68 | @{if PREF.credentials} 69 |
70 |
@(Your default credentials are below. You can change them after you sign in.)
71 |
72 |
@(Name)
73 |
@{PREF.credentials.login}
74 |
75 |
76 |
@(Password)
77 |
@{PREF.credentials.password}
78 |
79 |
80 | @{fi} 81 |
82 |
@(Login name)
83 |
@(Password)
84 |
85 | 86 |
87 |
88 |
89 |
All rights reserved © 2016-@{NOW.getFullYear()}
90 | www.totaljs.com 91 |
92 |
93 |
94 |
95 | 96 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /views/mails/summarization.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Summarization 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 20 |
21 | 22 | 23 | 85 | 86 |
24 |
@{config.name}: @(Daily summarization)
25 |
26 |
@{model.date.format('@(dd. MMMM yyyy)')}
27 |
@(Yesterday were running:) @{model.length.pluralize(@('# applications', '# application', '# applications', '# applications'))}
28 | @{foreach m in model} 29 |
30 | 31 |
CPU
32 |
MEM
33 |
34 |
35 |
@(Restarts)
36 |
: @{m.restarts || 0}x
37 |
38 |
39 |
@(Errors)
40 |
: @{m.errors || 0}x
41 |
42 |
43 |
@(Max. CPU)
44 |
: @{m.cpu.format(1)}%
45 |
46 |
47 |
@(Max. RSS)
48 |
: @{m.memory.filesize()}
49 |
50 | @{if m.online} 51 |
52 |
@(Max. online visitors)
53 |
: @{m.online.format(0)}x
54 |
55 |
56 |
@(Visitors)
57 |
: @{m.visitors.format(0)}x
58 |
59 |
60 |
@(Unique month visitors)
61 |
: @{m.uniquemonth.format(0)}x
62 |
63 | @{fi} 64 |
65 |
66 |
 
67 |
68 |
@(Max. connections)
69 |
: @{m.connections.format(0)}x
70 |
71 |
72 |
@(Max. open files)
73 |
: @{m.openfiles.format(0)}x
74 |
75 |
76 |
@(Disk usage)
77 |
: @{m.hdd.filesize()}
78 |
79 |
80 |
81 | @{end} 82 |
83 |
© 2017-@{NOW.getFullYear()} @{config.name}
84 |
87 |
88 | 89 | --------------------------------------------------------------------------------