diff --git a/basic.sh b/basic.sh index d222e77..91504c5 100755 --- a/basic.sh +++ b/basic.sh @@ -4,8 +4,8 @@ set -e SOURCE_DIR=$(realpath $(dirname "${0}"))/basic # Install packages -apk --no-cache add --virtual .useful curl git file htop libressl openssh-server openssh-sftp-server -apk --no-cache add docker gettext kbd-misc python3 nginx +apk --no-cache add --virtual .useful git file htop openssh-server openssh-sftp-server +apk --no-cache add curl docker gettext kbd-misc libressl python3 py3-dnspython py3-jinja2 py3-requests py3-werkzeug nginx # Copy profile files and settings mkdir -p /root/.config/htop /root/.ssh @@ -38,17 +38,13 @@ cp ${SOURCE_DIR}/etc/nginx/nginx.conf /etc/nginx/nginx.conf mkdir /etc/acme.sh.d wget https://raw.githubusercontent.com/Neilpang/acme.sh/master/acme.sh -O /usr/bin/acme.sh sed -i 's|$HOME/.$PROJECT_NAME|/etc/acme.sh.d|' /usr/bin/acme.sh -cp ${SOURCE_DIR}/etc/periodic/daily/acme-sh /etc/periodic/daily/acme-sh -chmod +x /usr/bin/acme.sh # Copy Spotter resources -mkdir /etc/spotter -cp ${SOURCE_DIR}/srv/config.json /srv/config.json -cp ${SOURCE_DIR}/usr/bin/spotter-appmgr /usr/bin/spotter-appmgr -cp -r ${SOURCE_DIR}/srv/portal /srv/portal +cp -r ${SOURCE_DIR}/srv/spotter /srv/spotter +ln -s /srv/spotter/cli.py /usr/bin/spotter-appmgr # Configure services -for SERVICE in consolefont crond nginx ntpd sshd; do +for SERVICE in consolefont crond nginx ntpd sshd spotter-appmgr; do rc-update add ${SERVICE} boot service ${SERVICE} start done @@ -61,5 +57,5 @@ service docker start # Create basic images docker build -t alpine ${SOURCE_DIR} -# Set dummy domain and generate related files -spotter-appmgr update-domain spotter.vm 443 +# Set dummy host and generate related files +spotter-appmgr update-host spotter.vm 443 diff --git a/basic/etc/init.d/spotter-appmgr b/basic/etc/init.d/spotter-appmgr new file mode 100755 index 0000000..3cbaf79 --- /dev/null +++ b/basic/etc/init.d/spotter-appmgr @@ -0,0 +1,6 @@ +#!/sbin/openrc-run + +command=/srv/spotter/wsgi.py +description="Spotter application manager" +pidfile=/var/run/spotter-appmgr.pid +start_stop_daemon_args="--background --make-pidfile --stderr /dev/null --stdout /dev/null" diff --git a/basic/etc/periodic/daily/acme-sh b/basic/etc/periodic/daily/acme-sh deleted file mode 100755 index 5b3d5e3..0000000 --- a/basic/etc/periodic/daily/acme-sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -[ -x /usr/bin/acme.sh ] && /usr/bin/acme.sh --cron >/dev/null diff --git a/basic/srv/config.json b/basic/srv/config.json deleted file mode 100644 index e0ce732..0000000 --- a/basic/srv/config.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "apps":{ - "ckan":{ - "host":"ckan", - "login":"N/A", - "password":"N/A", - "tiles":[], - "tiles-shown":false - }, - "crisiscleanup":{ - "host":"cc", - "login":"N/A", - "password":"N/A", - "tiles":[], - "tiles-shown":false - }, - "cts":{ - "host":"cts", - "login":"N/A", - "password":"N/A", - "tiles":[], - "tiles-shown":false - }, - "frontlinesms":{ - "host":"sms", - "login":"N/A", - "password":"N/A", - "tiles":[], - "tiles-shown":false - }, - "gnuhealth":{ - "host":"gh", - "login":"N/A", - "password":"N/A", - "tiles":["gnuhealth-clients"], - "tiles-shown":false - }, - "kanboard":{ - "host":"kb", - "login":"N/A", - "password":"N/A", - "tiles":["kanboard-mobile"], - "tiles-shown":false - }, - "mifosx":{ - "host":"mifosx", - "login":"N/A", - "password":"N/A", - "tiles":["mifosx-mobile"], - "tiles-shown":false - }, - "motech":{ - "host":"motech", - "login":"N/A", - "password":"N/A", - "tiles":[], - "tiles-shown":false - }, - "opendatakit":{ - "host":"odk", - "login":"N/A", - "password":"N/A", - "tiles":["opendatakit-clients"], - "tiles-shown":false - }, - "opendatakit-build":{ - "host":"odkbuild", - "login":"N/A", - "password":"N/A", - "tiles":[], - "tiles-shown":false - }, - "openmapkit":{ - "host":"omk", - "login":"N/A", - "password":"N/A", - "tiles":["geoodk-clients", "openmapkit-clients"], - "tiles-shown":false - }, - "pandora":{ - "host":"pandora", - "login":"N/A", - "password":"N/A", - "tiles":[], - "tiles-shown":false - }, - "sahana":{ - "host":"sahana", - "login":"N/A", - "password":"N/A", - "tiles":[], - "tiles-shown":false - }, - "sahana-demo":{ - "host":"sahana-demo", - "login":"N/A", - "password":"N/A", - "tiles":[], - "tiles-shown":false - }, - "sambro":{ - "host":"sambro", - "login":"N/A", - "password":"N/A", - "tiles":["sambro-mobile"], - "tiles-shown":false - }, - "seeddms":{ - "host":"dms", - "login":"N/A", - "password":"N/A", - "tiles":[], - "tiles-shown":false - }, - "sigmah":{ - "host":"sigmah", - "login":"N/A", - "password":"N/A", - "tiles":[], - "tiles-shown":false - }, - "ushahidi":{ - "host":"ush", - "login":"N/A", - "password":"N/A", - "tiles":["ushahidi-mobile"], - "tiles-shown":false - } - }, - "host":{ - "domain":"spotter.vm", - "port": "443" - } -} diff --git a/basic/srv/portal/css/style.css b/basic/srv/portal/css/style.css deleted file mode 100644 index 9790c24..0000000 --- a/basic/srv/portal/css/style.css +++ /dev/null @@ -1,86 +0,0 @@ -* { - margin: 0; - padding: 0; - border: 0; -} - -body { - font-family: 'Calibri', 'Verdana', 'Tahoma', sans-serif; - background-color: silver; - color: black; - line-height: 150%; - margin: 25px 30px; -} - -a { - text-decoration: none; -} - -h1, h2 { - font-size: 150%; - font-weight: normal; -} - -h2 a { - color: inherit; -} - -h2 img { - float: right; - margin-left: 10px; - margin-bottom:10px; - width: 100px; - height: 100px; -} - -ul { - margin-left: 30px; -} - -header { - color: white; -} - -header h1 { - font-weight: bold; -} - -header p { - padding: 0px; - margin: 0px; -} - -.c { - background-color: white; - position: relative; - min-width: 365px; - max-width: 365px; - width: 90%; - float: left; - min-height: 175px; - margin-top: 13px; - margin-right: 13px; - border: solid 1px black; - padding: 10px; - display: none; -} - -.c2 { - max-width: 765px; - width: 95%; -} - -.visible { - display: initial; -} - -.ico { - margin-right: 5px; - width: 20px; - height: 20px; - vertical-align: top; -} - -.cleaner { - clear: both; -} diff --git a/basic/srv/portal/index.html b/basic/srv/portal/index.html deleted file mode 100644 index de01148..0000000 --- a/basic/srv/portal/index.html +++ /dev/null @@ -1,308 +0,0 @@ - - - - - - - - - Cluster NGO - - - - - - -
-

CLUSTER NGO

-

Sada softwarových nástrojů určená pro krizový management.

-
- -
-

Sahana EDENSahana EDEN

-

Registr kontaktů asociací, organizací, jednotek zaměstnanců, dobrovolníků, Registr prostředků, materiálních zdrojů určených pro činnost v krizových situacích, logistika krizového zboží ve skladištích, úkrytech, organizace lidských zdrojů, diobrovolníků, mapová vizualizace pro lokalizaci a popis krizové události a mnoho dalších funkcí.

- -
- -
-

Sahana EDEN DEMOSahana EDEN DEMO

-

Přístup určený k bezpečnému vyzkoušení aplikace. Zde můžete přidávat i mazat testovací data.

- -
- -
-

Sahana EDEN SAMBROSahana EDEN SAMBRO

-

Samostatná instance Sahana EDEN s šablonou SAMBRO.

- -
- -
-

SAMBRO MobileSAMBRO Mobile

-

Mobilní klient k aplikaci Sahana EDEN.
- IOSIOS 6.0 a vyšší
- AndroidAndroid 4.0 a vyšší -

- -
- -
-

Crisis CleanupCrisis Cleanup

-

Mapování krizové pomoci při odstraňování následků katastrof a koordinaci práce. Jde o majetek, ne o lidi.

- -
- -
-

CKANCKAN

-

Repository management a datová analýza pro vytváření otevřených dat.

- -
- -
-

Open Data KitODK Build

-

Sběr dat s pomocí smartphone.
Aplikace pro návrh formulářů
-

XLSForm - online konverter XLS.
- ODK Form UploaderODK Form Uploader
- ODK ValidateODK Validate

-
- -
-

Open Data KitODK Collect

-

Mobilní aplikace
- ODK CollectODK Collect pro Android
- ODK BriefcaseODK Briefcase
-

- -
- -
-

Open Data KitODK Aggregate

-

Sběr dat s pomocí smartphone.
- GeoODK Collect - náhrada papírových dotazníků smartphonem. -

- -
- -
-

Open Map KitOpenMapKit Server

-

Sběr dat s pomocí smartphone.
-

-
- -
-

GeoODK CollectGeoODK Collect

-

Mobilní aplikace
- GeoODK CollectGeoODK Collect pro Android -

- -
- -
-

Open Map KitOpenMapKit

-

Mobilní aplikace
- ODK CollectODK Collect pro Android
- AndroidOpenMapKit pro Android 4.1 a vyšší -

- -
- -
-

FrontlineSMSFrontlineSMS

-

SMS messaging přes veřejné datové brány

- -
- -
-

SeedDMSSeedDMS

-

Dokument management na dokumentaci a projektovou dokumentaci

- -
- -
-

Pan.do/raPan.do/ra

-

Media management na foto a video z krizové události. Tvorba metadat, komentářů, lokalizace v čase a na mapě.

- -
- -
-

UshahidiUshahidi

-

Reakce na krizovou událost. Shromažďujte zprávy od obětí a pracovníků v terénu prostřednictvím SMS, e-mailu, webu, Twitteru.

- -
- -
-

UshahidiUshahidi Mobile

-

Mobilní aplikace Ushahidi pro
- IOSIOS 9.0 a vyšší
- AndroidAndroid 4.4 a vyšší -

- -
- -
-

SMS Sync GatewaySMS Sync Gateway

-

Mobilní aplikace pro
- AndroidAndroid 2.3 a vyšší -

-
- -
-

KanboardKanboard

-

Usnadňuje tvorbu a řízení projektů s pomocí Kanban metodiky.

- -
- -
-

KanboardKanboard Mobile

-

Mobilní aplikace
- KanBoardKanBoard client pro Android 4.1 a vyšší
- AndroidKandroid pro Android 4.2 a vyšší -

- -
- -
-

CTSCTS

-

Logistika hmotné pomoci pro humanitární potřeby.

- -
- -
-

GNU HealthGNU Health

-

Zdravotní a nemocniční informační systém.

- -
- -
-

GNU HealthGNU Health klienti

-

Klientské aplikace platformy Tryton GNU Health pro
- WindowsWindows
- MacOSMacOS
- LinuxLinux -

- -
- -
-

SigmahSigmah

-

Rozpočtování získávání finančních prostředků.

- -
- -
-

MotechMotech

-

Integrace zdravotnických a komunikačních služeb.

- -
- -
-

Mifos XMifos X

-

Nástroj na rozvojovou, humanitární pomoc a mikrofinancování.

- -
- -
-

Mifos XMifos X

-

Mobilní aplikace
- Mifos XMifos X client pro Android 3.0 a vyšší
-

- -
- -
-

diaspora*diaspora*

-

Autonomní sociání síť s možností propojení do cizích sociálních sítí.

- -
- -
-

OpenIDOpenID

-

Pro ověření identity budete potřebovat účet OpenID. Zaregistrujte se. Registraci využijete v software Sahana EDEN.

-
- -
-

POSMPOSM

-

Portable Open Street Map - softwarový balík na offline používání OpenStreet Map v samostatné virtuální image.

-
- -
-

Cluster SpotterCluster Spotter

-

Info o Misi a Vizi projektu, včetně kontaktu. Zachovejte data bezpečná a neposkytujte je nepovolaným osobám.
- CC 4.0 CZ by TS. Content is based on PD, CC, GNU/GPL. Brand names, trademarks belong to their respective holders. -

-
- -
- - \ No newline at end of file diff --git a/basic/srv/portal/js/script.js b/basic/srv/portal/js/script.js deleted file mode 100644 index 0fee73a..0000000 --- a/basic/srv/portal/js/script.js +++ /dev/null @@ -1,21 +0,0 @@ -$(function() { - $.getJSON('config.json', function(data) { - $.each(data.apps, function(app, appdata) { - if (!appdata['tiles-shown']) - return true; - var div = $('#'+app).show(); - div.find('.login').text(appdata.login); - div.find('.password').text(appdata.password); - $.each(appdata.tiles, function(idx, tile) { - $('#'+tile).show(); - }); - }); - var host = data.host.domain + (data.host.port != '443' ? ':'+data.host.port : '') - $('a').each(function(){ - $(this).attr('href', $(this).attr('href').replace('{host}', host)); - }); - $('span').each(function(){ - $(this).text($(this).text().replace('{host}', host)); - }); - }); -}); diff --git a/basic/srv/spotter/appmgr/__init__.py b/basic/srv/spotter/appmgr/__init__.py new file mode 100644 index 0000000..7170f76 --- /dev/null +++ b/basic/srv/spotter/appmgr/__init__.py @@ -0,0 +1,336 @@ +# -*- coding: utf-8 -*- + +import json +import os +import shutil +import subprocess + +from . import confupdater +from . import tools +from . import validator + +VERSION = '0.0.1' + +CONF_FILE = '/srv/spotter/config.json' +ISSUE_FILE = '/etc/issue' +NGINX_DIR = '/etc/nginx/conf.d' +ACME_CRON = '/etc/periodic/daily/acme-sh' +CERT_PUB_FILE = '/etc/ssl/certs/services.pem' +CERT_KEY_FILE = '/etc/ssl/private/services.key' + +NGINX_TEMPLATE = '''server {{ + listen [::]:{port} ssl http2; + server_name {host}.{domain}; + + access_log /var/log/nginx/{app}.access.log; + error_log /var/log/nginx/{app}.error.log; + + location / {{ + proxy_pass http://{ip}:8080; + }} + + error_page 502 /error.html; + location = /error.html {{ + root /srv/spotter; + }} + + location = /spotter-ping {{ + add_header Content-Type text/plain; + return 200 "spotter-pong"; + }} +}} +''' + +NGINX_DEFAULT_TEMPLATE = '''server {{ + listen [::]:80 default_server ipv6only=off; + + location / {{ + return 301 https://$host:{port}$request_uri; + }} + + location /.well-known/acme-challenge/ {{ + root /etc/acme.sh.d; + }} + + location = /spotter-ping {{ + add_header Content-Type text/plain; + return 200 "spotter-pong"; + }} +}} + +server {{ + listen [::]:{port} ssl http2 default_server ipv6only=off; + + location / {{ + proxy_pass http://127.0.0.1:8080; + }} + + location /static {{ + root /srv/spotter; + }} + + error_page 502 /error.html; + location = /error.html {{ + root /srv/spotter; + }} + + location = /spotter-ping {{ + add_header Content-Type text/plain; + return 200 "spotter-pong"; + }} +}} +''' + +ISSUE_TEMPLATE = ''' +\x1b[1;32m _____ _ _ __ ____ __ + / ____| | | | | \\\\ \\\\ / / \\\\/ | + | (___ _ __ ___ | |_| |_ ___ _ _\\\\ \\\\ / /| \\\\ / | + \\\\___ \\\\| '_ \\\\ / _ \\\\| __| __/ _ \\\\ '__\\\\ \\\\/ / | |\\\\/| | + ____) | |_) | (_) | |_| || __/ | \\\\ / | | | | + |_____/| .__/ \\\\___/ \\\\__|\\\\__\\\\___|_| \\\\/ |_| |_| + | | + |_|\x1b[0m + + + + + \x1b[1;33mUPOZORNĚNÍ:\x1b[0m Neoprávněný přístup k tomuto zařízení je zakázán. + Musíte mít výslovné oprávnění k přístupu nebo konfiguraci tohoto zařízení. + Neoprávněné pokusy a kroky k přístupu nebo používání tohoto systému mohou mít + za následek občanské nebo trestní sankce. + + + \x1b[1;33mCAUTION:\x1b[0m Unauthozired access to this device is prohibited. + You must have explicit, authorized permission to access or configure this + device. Unauthorized attempts and actions to access or use this system may + result in civil or criminal penalties. + + + + + Pro přístup k aplikacím otevřete URL \x1b[1mhttps://{host}\x1b[0m ve Vašem + internetovém prohlížeči. + + + + + + +\x1b[0;30m +''' + +ACME_CRON_TEMPLATE = '''#!/bin/sh + +[ -x /usr/bin/acme.sh ] && /usr/bin/acme.sh --cron >/dev/null +''' + +class AppMgr: + def __init__(self): + # Load JSON configuration + with open(CONF_FILE, 'r') as f: + self.conf = json.load(f) + self.domain = self.conf['host']['domain'] + self.port = self.conf['host']['port'] + + def save_conf(self): + # Save a sorted JSON configuration object with indentation + with open(CONF_FILE, 'w') as f: + json.dump(self.conf, f, sort_keys=True, indent=4) + + def update_login(self, app, login, password): + # Update login and password for an app in the configuration + if not validator.is_valid_app(app, self.conf): + raise validator.InvalidValueException('app', app) + if login is not None: + self.conf['apps'][app]['login'] = login + if password is not None: + self.conf['apps'][app]['password'] = password + self.save_conf() + + def show_tiles(self, app): + # Update visibility for the app in the configuration + if not validator.is_valid_app(app, self.conf): + raise validator.InvalidValueException('app', app) + self.conf['apps'][app]['visible'] = True + self.save_conf() + + def hide_tiles(self, app): + # Update visibility for the app in the configuration + if not validator.is_valid_app(app, self.conf): + raise validator.InvalidValueException('app', app) + self.conf['apps'][app]['visible'] = False + self.save_conf() + + def start_app(self, app): + # Start the actual app service + if not validator.is_valid_app(app, self.conf): + raise validator.InvalidValueException('app', app) + tools.start_service(app) + + def stop_app(self, app): + # Stop the actual app service + if not validator.is_valid_app(app, self.conf): + raise validator.InvalidValueException('app', app) + tools.stop_service(app) + # Stop the app service's dependencies if they are not used by another running app + deps = self.build_deps_tree() + for dep in self.get_app_deps(app): + if not any([tools.is_service_started(d) for d in deps[dep]]): + tools.stop_service(dep) + + def build_deps_tree(self): + # Fisrt, build a dictionary of {app: [needs]} + needs = {} + for app in self.conf['apps']: + needs[app] = self.get_app_deps(app) + # Then reverse it to {need: [apps]} + deps = {} + for app, need in needs.items(): + for n in need: + deps.setdefault(n, []).append(app) + return deps + + def get_app_deps(self, app): + # Get "needs" line from init script and split it to list, skipping first two elements (docker, net) + try: + with open(os.path.join('/etc/init.d', app), 'r') as f: + return [l.split()[2:] for l in f.readlines() if l.startswith('\tneed')][0] + except: + return [] + + def enable_autostart(self, app): + # Add the app to OpenRC default runlevel + if not validator.is_valid_app(app, self.conf): + raise validator.InvalidValueException('app', app) + subprocess.run(['/sbin/rc-update', 'add', app]) + + def disable_autostart(self, app): + # Remove the app from OpenRC default runlevel + if not validator.is_valid_app(app, self.conf): + raise validator.InvalidValueException('app', app) + subprocess.run(['/sbin/rc-update', 'del', app]) + + def register_proxy(self, app): + # Rebuild nginx configuration using IP of referenced app container and reload nginx + if not validator.is_valid_app(app, self.conf): + raise validator.InvalidValueException('app', app) + self.update_proxy_conf(app, tools.get_container_ip(app)) + tools.reload_nginx() + + def update_proxy_conf(self, app, ip): + with open(os.path.join(NGINX_DIR, '{}.conf'.format(app)), 'w') as f: + f.write(NGINX_TEMPLATE.format(app=app, host=self.conf['apps'][app]['host'], ip=ip, domain=self.domain, port=self.port)) + + def unregister_proxy(self, app): + # Remove nginx configuration to prevent proxy mismatch when the container IP is reassigned to another container + if not validator.is_valid_app(app, self.conf): + raise validator.InvalidValueException('app', app) + self.update_proxy_conf(app, tools.NULL_IP) + tools.reload_nginx() + + def update_host(self, domain, port, restart_nginx=True): + # Update domain and port and rebuild all configurtion. Defer nginx restart when updating from web interface + if not validator.is_valid_domain(domain): + raise validator.InvalidValueException('domain', domain) + if not validator.is_valid_port(port): + raise validator.InvalidValueException('port', port) + self.domain = self.conf['host']['domain'] = domain + self.port = self.conf['host']['port'] = port + self.save_conf() + self.rebuild_nginx(restart_nginx) + self.rebuild_issue() + self.update_apps_urls() + + def rebuild_nginx(self, restart_nginx): + # Rebuild nginx config for the portal app + with open(os.path.join(NGINX_DIR, 'default.conf'), 'w') as f: + f.write(NGINX_DEFAULT_TEMPLATE.format(port=self.port)) + # Unregister nginx proxy for apps (will be repopulated on app restart) + for app in self.conf['apps']: + self.update_proxy_conf(app, tools.NULL_IP) + # Restart nginx to properly bind the new listen port + if restart_nginx: + tools.restart_service('nginx') + + def rebuild_issue(self): + # Compile the HTTPS host displayed in terminal banner + host = self.domain + # If the dummy host is used, take an IP address of a primary interface instead + if self.domain == 'spotter.vm': + host = tools.get_local_ipv4() + if not host: + host = tools.get_local_ipv6() + if not host: + host = '127.0.0.1' + # Show port number only when using the non-default HTTPS port + if self.port != '443': + host += ':{}'.format(self.port) + # Rebuild the terminal banner + with open(ISSUE_FILE, 'w') as f: + f.write(ISSUE_TEMPLATE.format(host=host)) + + def update_apps_urls(self): + # Update configuration for respective applications + confupdater.update_url() + # Restart currently running apps in order to update config and re-register nginx proxy + for app in self.conf['apps']: + if tools.is_service_started(app): + tools.restart_service(app) + + def update_common(self, email, gmaps_api_key): + # Update common configuration values + if email != None: + # Update email + if not validator.is_valid_email(email): + raise validator.InvalidValueException('email', email) + self.conf['common']['email'] = email + confupdater.update_email(email) + if gmaps_api_key != None: + # Update Google Maps API key + self.conf['common']['gmaps-api-key'] = gmaps_api_key + confupdater.update_gmaps_api_key(gmaps_api_key) + # Save config to file + self.save_conf() + # Restart currently running apps in order to update config + for app in self.conf['apps']: + if tools.is_service_started(app): + tools.restart_service(app) + + def request_cert(self): + # Remove all possible conflicting certificates requested in the past + certs = [i for i in os.listdir('/etc/acme.sh.d') if i not in ('account.conf', 'ca', 'http.header')] + for cert in certs: + if cert != self.domain: + subprocess.run(['/usr/bin/acme.sh', '--remove', '-d', cert]) + # Compile an acme.sh command for certificate requisition only if the certificate hasn't been requested before + if not os.path.exists(os.path.join('/etc/acme.sh.d', self.domain)): + cmd = ['/usr/bin/acme.sh', '--issue', '-d', self.domain] + for app in self.conf['apps']: + cmd += ['-d', '{}.{}'.format(self.conf['apps'][app]['host'], self.domain)] + cmd += ['-w', '/etc/acme.sh.d'] + # Request the certificate + subprocess.run(cmd, check=True) + # Otherwise just try to renew + else: + # Acme.sh returns code 2 on skipped renew + try: + subprocess.run(['/usr/bin/acme.sh', '--renew', '-d', self.domain], check=True) + except subprocess.CalledProcessError as e: + if e.returncode != 2: + raise + # Install the issued certificate + subprocess.run(['/usr/bin/acme.sh', '--install-cert', '-d', self.domain, '--key-file', CERT_KEY_FILE, '--fullchain-file', CERT_PUB_FILE, '--reloadcmd', '/sbin/service nginx reload'], check=True) + # Install acme.sh cronjob + with open(ACME_CRON, 'w') as f: + f.write(ACME_CRON_TEMPLATE) + + def install_cert(self, public_file, private_file): + # Remove acme.sh cronjob + if os.path.exists(ACME_CRON): + os.unlink(ACME_CRON) + # Copy certificate files + shutil.copyfile(public_file, CERT_PUB_FILE) + shutil.copyfile(private_file, CERT_KEY_FILE) + os.chmod(CERT_KEY_FILE, 0o640) + # Reload nginx + tools.reload_nginx() diff --git a/basic/srv/spotter/appmgr/confupdater.py b/basic/srv/spotter/appmgr/confupdater.py new file mode 100644 index 0000000..5683eda --- /dev/null +++ b/basic/srv/spotter/appmgr/confupdater.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +import os +import shutil +import subprocess + +from . import tools + +TMP_FILE = '/tmp/confupdater.tmp' + +def replace_file_line(filename, oldline, newline): + with open(filename, 'r') as conf, open(TMP_FILE, 'w') as tmp: + for line in conf: + # Find line starting with oldline + if line.startswith(oldline): + # Replace te line with oldline, newline, \n (to not repeat the oldline in newline) + tmp.write(oldline) + tmp.write(newline) + tmp.write('\n') + # Dump the rest of the file and break the loop + tmp.write(conf.read()) + break + else: + tmp.write(line) + # Copy the file contents to the original file (preserves permissions of the original file) + shutil.copyfile(TMP_FILE, filename) + os.unlink(TMP_FILE) + +def run_mysql_query(query, database): + if not maria_started: + tools.start_service('mariadb') + subprocess.run(['docker', 'exec', '-i', 'mariadb', 'mysql', '-e', query, database]) + if not maria_started: + tools.stop_service('mariadb') + +def update_gmaps_api_key(api_key): + # CKAN + replace_file_line('/srv/ckan/conf/ckan.ini', 'ckanext.geoview.gapi_key = ', api_key) + # Crisis Cleanup + replace_file_line('/srv/crisiscleanup/conf/boot.rb', 'ENV[\'GOOGLE_MAPS_API_KEY\'] = ', api_key) + # Pan.do/ra + replace_file_line('/srv/pandora/conf/local_settings.py', 'GOOGLE_API_KEY = ', '\'{}\''.format(api_key)) + # Sahana + replace_file_line('/srv/sahana/conf/000_config.py', 'settings.gis.api_google = ', '"{}"'.format(api_key)) + # Sahana Demo + replace_file_line('/srv/sahana-demo/conf/000_config.py', 'settings.gis.api_google = ', '"{}"'.format(api_key)) + # SAMBRO + replace_file_line('/srv/sambro/conf/000_config.py', 'settings.gis.api_google = ', '"{}"'.format(api_key)) + # Sigmah + replace_file_line('/srv/sigmah/conf/sigmah.properties', 'maps.key=', api_key) + # Ushahidi + replace_file_line('/srv/ushahidi/conf/config.json', ' "google_analytics_id": ', '"{}"'.format(api_key)) + +def update_email(email): + # CKAN + replace_file_line('/srv/ckan/conf/ckan.ini', 'smtp.mail_from = ', email) + replace_file_line('/srv/ckan-datapusher/conf/datapusher_settings.py', 'FROM_EMAIL = ', '\'{}\''.format(email) + # Crisis Cleanup + replace_file_line('/srv/crisiscleanup/conf/initializers/devise.rb', ' config.mailer_sender = ', '\'{}\''.format(email) + # CTS + replace_file_line('/srv/cts/conf/spotter.py', 'SERVER_EMAIL = ', '\'{}\''.format(email) + # GNU Health + replace_file_line('/srv/gnuhealth/conf/trytond.conf', 'from = ', email) + # KanBoard + replace_file_line('/srv/kanboard/conf/config.php', 'define(\'MAIL_FROM\', ', '\'{}\');'.format(email)) + # Mifos X + query = 'UPDATE `c_external_service_properties` SET `value` = "{}" WHERE `external_service_id` = 2 and `name` LIKE "username";'.format(email) + run_mysql_query(query, 'mifostenant-default') + # Sahana + replace_file_line('/srv/sahana/conf/000_config.py', 'settings.mail.sender = ', '"{}"'.format(email)) + replace_file_line('/srv/sahana/conf/000_config.py', 'settings.mail.approver = ', '"{}"'.format(email)) + # Sahana Demo + replace_file_line('/srv/sahana-demo/conf/000_config.py', 'settings.mail.sender = ', '"{}"'.format(email)) + replace_file_line('/srv/sahana-demo/conf/000_config.py', 'settings.mail.approver = ', '"{}"'.format(email)) + # SAMBRO + replace_file_line('/srv/sambro/conf/000_config.py', 'settings.mail.sender = ', '"{}"'.format(email)) + replace_file_line('/srv/sambro/conf/000_config.py', 'settings.mail.approver = ', '"{}"'.format(email)) + # SeedDMS + replace_file_line('/srv/seeddms/conf/settings.xml', ' '.format(email)) + # Sigmah + replace_file_line('/srv/sigmah/conf/sigmah.properties', 'mail.from.address=', email) + replace_file_line('/srv/sigmah/conf/sigmah.properties', 'mail.support.to=', email) + # Ushahidi + email_json = '{\"incoming_type\":\"IMAP\",\"incoming_server\":\"localhost\",\"incoming_port\":143,\"incoming_security\":\"None\",\"incoming_username\":\"{}\",\"incoming_password\":\"password\",\"outgoing_type\":\"SMTP\",\"outgoing_server\":\"postfix\",\"outgoing_port\":25,\"outgoing_security\":\"None\",\"outgoing_username\":\"{}\",\"outgoing_password\":\"password\",\"from\":\"{}\",\"from_name\":\"Ushahidi\"}' + query = 'UPDATE `config` SET `config_value` = "{}" WHERE `group_name` LIKE "data-provider" AND `config_key` LIKE "email";'.format(email_json) + run_mysql_query(query, 'ushahidi') + +def update_url(host): + # CKAN + replace_file_line('/srv/ckan/conf/ckan.ini', 'ckan.site_url = ', 'https://ckan.{}'.format(host)) + # Motech + replace_file_line('/srv/motech/conf/config/motech-settings.properties', 'server.url=', 'https://motech.{}'.format(host)) + # Pan.do/ra + replace_file_line('/srv/pandora/conf/config.jsonc', ' "url": ', '"pandora.{}"'.format(host)) + # Sahana + replace_file_line('/srv/sahana/conf/000_config.py', 'settings.base.public_url = ', '"https://sahana.{}"'.format(host)) + # Sahana Demo + replace_file_line('/srv/sahana-demo/conf/000_config.py', 'settings.base.public_url = ', '"https://sahana-demo.{}"'.format(host)) + # SAMBRO + replace_file_line('/srv/sambro/conf/000_config.py', 'settings.base.public_url = ', '"https://sambro.{}"'.format(host)) + # Ushahidi + replace_file_line('/srv/ushahidi/conf/config.json', ' "backend_url": ', '"https://ush.{}/platform",'.format(host)) + api_url = '\"https:\\/\\/ush.{}\\/platform\\/api\\/v3\\/config\\/data-provider\"' + query = 'UPDATE `config` SET `config_value` = "{}" WHERE `group_name` LIKE "data-provider" AND `config_key` LIKE "url";'.format(api_url) + run_mysql_query(query, 'ushahidi') diff --git a/basic/srv/spotter/appmgr/tools.py b/basic/srv/spotter/appmgr/tools.py new file mode 100644 index 0000000..03651d7 --- /dev/null +++ b/basic/srv/spotter/appmgr/tools.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +import dns.exception +import dns.resolver +import os +import requests +import socket +import ssl +import subprocess + +NULL_IP = '[100::1]' + +def get_container_ip(app): + # Return an IP address of a container. If the container is not running, return address from IPv6 discard prefix instead + try: + return subprocess.run(['/usr/bin/docker', 'inspect', '-f', '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}', app], check=True, stdout=subprocess.PIPE).stdout.decode().strip() + except: + return NULL_IP + +def get_local_ipv4(): + # Return first routable IPv4 address + try: + return subprocess.run(['/sbin/ip', 'route', 'get', '1'], check=True, stdout=subprocess.PIPE).stdout.decode().split()[-1] + except: + return None + +def get_local_ipv6(): + # Return first routable IPv6 address + try: + return subprocess.run(['/sbin/ip', 'route', 'get', '2003::'], check=True, stdout=subprocess.PIPE).stdout.decode().split()[-3] + except: + return None + +def get_external_ip(family): + # Return external IP address of given family via 3rd party service + allowed_gai_family = requests.packages.urllib3.util.connection.allowed_gai_family + try: + requests.packages.urllib3.util.connection.allowed_gai_family = lambda: family + return requests.get('http://tools.dasm.cz/myip.php', timeout=5).text + except: + return None + finally: + requests.packages.urllib3.util.connection.allowed_gai_family = allowed_gai_family + +def get_external_ipv4(): + # Return external IPv4 address + return get_external_ip(socket.AF_INET) + +def get_external_ipv6(): + # Return external IPv6 address + return get_external_ip(socket.AF_INET6) + +resolver = dns.resolver.Resolver() +resolver.timeout = 3 +resolver.lifetime = 3 +resolver.nameservers = ['8.8.8.8', '8.8.4.4', '2001:4860:4860::8888', '2001:4860:4860::8844'] + +def resolve_ip(domain, type): + # Resolve domain name using Google Public DNS + try: + return resolver.query(domain, type)[0].address + except dns.exception.Timeout: + raise + except: + return None + +def ping_url(url): + try: + return requests.post('http://tools.dasm.cz/spotter-ping.php', data = {'url': url}, timeout=5).text == 'spotter-pong' + except requests.exceptions.Timeout: + raise + except: + return False + +def is_service_started(app): + # Check OpenRC service status without calling any binary + return os.path.exists(os.path.join('/run/openrc/started', app)) + +def is_service_autostarted(app): + # Check OpenRC service enablement + return os.path.exists(os.path.join('/etc/runlevels/default', app)) + +def start_service(service): + subprocess.run(['/sbin/service', service, 'start'], check=True) + +def stop_service(service): + subprocess.run(['/sbin/service', service, 'stop'], check=True) + +def restart_service(service): + subprocess.run(['/sbin/service', service, 'restart']) + +def reload_nginx(): + subprocess.run(['/sbin/service', 'nginx', 'reload']) + +def get_cert_info(): + data = ssl._ssl._test_decode_cert('/etc/ssl/certs/services.pem') + data['subject'] = dict(data['subject'][i][0] for i in range(len(data['subject']))) + data['issuer'] = dict(data['issuer'][i][0] for i in range(len(data['issuer']))) + return data diff --git a/basic/srv/spotter/appmgr/validator.py b/basic/srv/spotter/appmgr/validator.py new file mode 100644 index 0000000..731f6e5 --- /dev/null +++ b/basic/srv/spotter/appmgr/validator.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +import re + +domain_re = re.compile(r'^(?!-)[a-z0-9-]{1,63}(? 0 and port < 65536 + except: + return False + +def is_valid_app(app, conf): + return app in conf['apps'] + +def is_valid_email(email): + parts = email.split('@') + if len(parts) != 2: + return False + return bool(box_re.match(parts[0])) and bool(domain_re.match(parts[1])) diff --git a/basic/srv/spotter/appmgr/wsgiapp.py b/basic/srv/spotter/appmgr/wsgiapp.py new file mode 100644 index 0000000..2bdaa54 --- /dev/null +++ b/basic/srv/spotter/appmgr/wsgiapp.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- + +import json +import os + +from werkzeug.exceptions import BadRequest, HTTPException +from werkzeug.routing import Map, Rule +from werkzeug.utils import redirect +from werkzeug.wrappers import Request, Response +from werkzeug.wsgi import ClosingIterator +from jinja2 import Environment, FileSystemLoader + +from . import AppMgr +from . import tools +from .validator import InvalidValueException + +class Lang: + lang = { + 'malformed_request': 'Byl zaslán chybný požadavek. Obnovte stránku a zkuste akci zopakovat.', + 'invalid_domain': 'Zadaný doménový název "{}" není platný.', + 'invalid_port': 'Zadaný port "{}" není platný.', + 'host_updated': 'Nastavení hostitele bylo úspěšně změněno. Přejděte na URL {} a pokračujte následujícími kroky.', + 'dns_record_does_not_exist': 'DNS záznam pro název "{}" neexistuje.', + 'dns_record_mismatch': 'DNS záznam pro název "{}" směřuje na IP {} místo očekávané {}.', + 'dns_timeout': 'Nepodařilo se kontaktovat DNS server. Zkontrolujte, zda má virtuální stroj přístup k internetu.', + 'dns_records_ok': 'DNS záznamy jsou nastaveny správně.', + 'http_host_not_reachable': 'Adresa {} není dostupná z internetu. Zkontrolujte nastavení síťových komponent.', + 'http_timeout': 'Nepodařilo se kontaktovat ping server. Zkontrolujte, zda má virtuální stroj přístup k internetu.', + 'http_hosts_ok': 'Síť je nastavena správně. Všechny aplikace na portu {} jsou z internetu dostupné.', + 'cert_file_missing': 'Nebyl vybrán soubor s certifikátem.', + 'key_file_missing': 'Nebyl vybrán soubor se soukromým klíčem.', + 'cert_request_error': 'Došlo k chybě při žádosti o certifikát. Zkontrolujte, zda je virtuální stroj dostupný z internetu na portu 80.', + 'cert_installed': 'Certifikát byl úspěšně nainstalován. Restartujte webový prohlížeč pro jeho načtení.', + 'common_updated': 'Nastavení aplikací bylo úspěšně změněno.', + 'app_started': 'Spuštěna (zastavit)', + 'app_stopped': 'Zastavena (spustit)', + 'stop_start_error': 'Došlo k chybě při spouštění/zastavování. Zkuste akci opakovat nebo restartuje virtuální stroj.', + } + + def __getattr__(self, key): + def function(*args): + return self.lang[key].format(*args) + return function + +class WSGIApp(object): + def __init__(self): + self.mgr = AppMgr() + self.lang = Lang() + self.jinja_env = Environment(loader=FileSystemLoader('/srv/spotter/templates'), autoescape=True, lstrip_blocks=True, trim_blocks=True) + self.jinja_env.globals.update(is_service_autostarted=tools.is_service_autostarted) + self.jinja_env.globals.update(is_service_started=tools.is_service_started) + + def __call__(self, environ, start_response): + return self.wsgi_app(environ, start_response) + + def wsgi_app(self, environ, start_response): + request = Request(environ) + response = self.dispatch_request(request) + response = response(environ, start_response) + # Defer nginx restart for /update-host request + if request.path == '/update-host': + return ClosingIterator(response, tools.restart_nginx) + return response + + def dispatch_request(self, request): + map = Map([ + Rule('/', endpoint='portal_view'), + Rule('/setup-host', endpoint='setup_host_view'), + Rule('/setup-apps', endpoint='setup_apps_view'), + Rule('/update-host', endpoint='update_host_action'), + Rule('/verify-dns', endpoint='verify_dns_action'), + Rule('/verify-https', endpoint='verify_http_action', defaults={'proto': 'https'}), + Rule('/verify-http', endpoint='verify_http_action', defaults={'proto': 'http'}), + Rule('/update-cert', endpoint='update_cert_action'), + Rule('/update-common', endpoint='update_common_action'), + Rule('/update-app-visibility', endpoint='update_app_visibility_action'), + Rule('/update-app-autostart', endpoint='update_app_autostart_action'), + Rule('/start-app', endpoint='start_app_action'), + Rule('/stop-app', endpoint='stop_app_action'), + ]) + adapter = map.bind_to_environ(request.environ) + try: + endpoint, values = adapter.match() + return getattr(self, endpoint)(request, **values) + except HTTPException as e: + return e + + def render_template(self, template_name, **context): + t = self.jinja_env.get_template(template_name) + return Response(t.render(context), mimetype='text/html') + + def render_json(self, data): + return Response(json.dumps(data), mimetype='application/json') + + def portal_view(self, request): + # Default view. If domain is set to the default dummy domain, redirects to first-run setup instead. + if self.mgr.domain == 'spotter.vm': + return redirect('/setup-host') + return self.render_template('portal.html', conf=self.mgr.conf) + + def setup_host_view(self, request): + # First-run setup view. + ex_ipv4 = tools.get_external_ipv4() + ex_ipv6 = tools.get_external_ipv6() + in_ipv4 = tools.get_local_ipv4() + in_ipv6 = tools.get_local_ipv6() + is_letsencrypt = os.path.exists('/etc/periodic/daily/acme-sh') + cert_info = tools.get_cert_info() + return self.render_template('setup-host.html', conf=self.mgr.conf, ex_ipv4=ex_ipv4, ex_ipv6=ex_ipv6, in_ipv4=in_ipv4, in_ipv6=in_ipv6, is_letsencrypt=is_letsencrypt, cert_info=cert_info) + + def setup_apps_view(self, request): + # Application manager view. + return self.render_template('setup-apps.html', conf=self.mgr.conf) + + def update_host_action(self, request): + # Update domain and port, then restart nginx (done via ClosingIterator in self.wsgi_app()) + try: + domain = request.form['domain'] + port = request.form['port'] + self.mgr.update_host(domain, port, False) + server_name = request.environ['HTTP_X_FORWARDED_SERVER_NAME'] + url = 'https://{}/setup-host'.format('{}:{}'.format(server_name, port) if port != '443' else server_name) + return self.render_json({'ok': self.lang.host_updated(url, url)}) + except BadRequest: + return self.render_json({'error': self.lang.malformed_request()}) + except InvalidValueException as e: + if e.args[0] == 'domain': + return self.render_json({'error': self.lang.invalid_domain(domain)}) + if e.args[0] == 'port': + return self.render_json({'error': self.lang.invalid_port(port)}) + + def verify_dns_action(self, request): + # Check if all FQDNs for all applications are resolvable and point to current external IP + domains = [self.mgr.domain]+['{}.{}'.format(self.mgr.conf['apps'][app]['host'], self.mgr.domain) for app in self.mgr.conf['apps']] + ipv4 = tools.get_external_ipv4() + ipv6 = tools.get_external_ipv6() + for domain in domains: + try: + a = tools.resolve_ip(domain, 'A') + aaaa = tools.resolve_ip(domain, 'AAAA') + if not a and not aaaa: + return self.render_json({'error': self.lang.dns_record_does_not_exist(domain)}) + if a and a != ipv4: + return self.render_json({'error': self.lang.dns_record_mismatch(domain, a, ipv4)}) + if aaaa and aaaa != ipv6: + return self.render_json({'error': self.lang.dns_record_mismatch(domain, aaaa, ipv6)}) + except: + return self.render_json({'error': self.lang.dns_timeout()}) + return self.render_json({'ok': self.lang.dns_records_ok()}) + + def verify_http_action(self, request, **kwargs): + # Check if all applications are accessible from the internet using 3rd party ping service + proto = kwargs['proto'] + domains = [self.mgr.domain]+['{}.{}'.format(self.mgr.conf['apps'][app]['host'], self.mgr.domain) for app in self.mgr.conf['apps']] + for domain in domains: + host = '{}:{}'.format(domain, self.mgr.port) if proto == 'https' and self.mgr.port != '443' else domain + url = '{}://{}/'.format(proto, host) + try: + if not tools.ping_url(url): + return self.render_json({'error': self.lang.http_host_not_reachable(url)}) + except: + return self.render_json({'error': self.lang.http_timeout()}) + return self.render_json({'ok': self.lang.http_hosts_ok(self.mgr.port if proto == 'https' else '80')}) + + def update_cert_action(self, request): + # Update certificate - either request via Let's Encrypt or manually upload files + try: + if request.form['method'] not in ['auto', 'manual']: + raise BadRequest() + if request.form['method'] == 'manual': + if not request.files['public']: + return self.render_json({'error': self.lang.cert_file_missing()}) + if not request.files['private']: + return self.render_json({'error': self.lang.key_file_missing()}) + request.files['public'].save('/tmp/public.pem') + request.files['private'].save('/tmp/private.pem') + self.mgr.install_cert('/tmp/public.pem', '/tmp/private.pem') + os.unlink('/tmp/public.pem') + os.unlink('/tmp/private.pem') + else: + self.mgr.request_cert() + except BadRequest: + return self.render_json({'error': self.lang.malformed_request()}) + except: + return self.render_json({'error': self.lang.cert_request_error()}) + return self.render_json({'ok': self.lang.cert_installed()}) + + def update_common_action(self, request): + try: + self.mgr.update_common(request.form['email'], request.form['gmaps-api-key']) + except BadRequest: + return self.render_json({'error': self.lang.malformed_request()}) + return self.render_json({'ok': self.lang.common_updated()}) + + def update_app_visibility_action(self, request): + try: + if request.form['value'] == 'true': + self.mgr.show_tiles(request.form['app']) + else: + self.mgr.hide_tiles(request.form['app']) + except (BadRequest, InvalidValueException): + return self.render_json({'error': self.lang.malformed_request()}) + return self.render_json({'ok': 'ok'}) + + def update_app_autostart_action(self, request): + try: + if request.form['value'] == 'true': + self.mgr.enable_autostart(request.form['app']) + else: + self.mgr.disable_autostart(request.form['app']) + except (BadRequest, InvalidValueException): + return self.render_json({'error': self.lang.malformed_request()}) + return self.render_json({'ok': 'ok'}) + + def start_app_action(self, request): + try: + self.mgr.start_app(request.form['app']) + except (BadRequest, InvalidValueException): + return self.render_json({'error': self.lang.malformed_request()}) + except: + return self.render_json({'error': self.lang.stop_start_error()}) + return self.render_json({'ok': self.lang.app_started()}) + + def stop_app_action(self, request): + try: + self.mgr.stop_app(request.form['app']) + except (BadRequest, InvalidValueException): + return self.render_json({'error': self.lang.malformed_request()}) + except: + return self.render_json({'error': self.lang.stop_start_error()}) + return self.render_json({'ok': self.lang.app_stopped()}) + +class InvalidRecordException(Exception): + pass diff --git a/basic/srv/spotter/cli.py b/basic/srv/spotter/cli.py new file mode 100755 index 0000000..529b6dd --- /dev/null +++ b/basic/srv/spotter/cli.py @@ -0,0 +1,96 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import argparse +import sys +sys.path.append('/srv/spotter') + +from appmgr import AppMgr + +parser = argparse.ArgumentParser(description='Spotter VM application manager') +subparsers = parser.add_subparsers() + +parser_update_login = subparsers.add_parser('update-login', help='Updates application login') +parser_update_login.set_defaults(action='update-login') +parser_update_login.add_argument('app', help='Application name') +parser_update_login.add_argument('login', help='Administrative login') +parser_update_login.add_argument('password', help='Administrative password') + +parser_show_tiles = subparsers.add_parser('show-tiles', help='Shows application tiles in Portal') +parser_show_tiles.set_defaults(action='show-tiles') +parser_show_tiles.add_argument('app', help='Application name') + +parser_hide_tiles = subparsers.add_parser('hide-tiles', help='Hides application tiles in Portal') +parser_hide_tiles.set_defaults(action='hide-tiles') +parser_hide_tiles.add_argument('app', help='Application name') + +parser_start_app = subparsers.add_parser('start-app', help='Start application including it\'s dependencies') +parser_start_app.set_defaults(action='start-app') +parser_start_app.add_argument('app', help='Application name') + +parser_stop_app = subparsers.add_parser('stop-app', help='Stops application including it\'s dependencies if they are not used by another running application') +parser_stop_app.set_defaults(action='stop-app') +parser_stop_app.add_argument('app', help='Application name') + +parser_enable_autostart = subparsers.add_parser('enable-autostart', help='Enables application autostart') +parser_enable_autostart.set_defaults(action='enable-autostart') +parser_enable_autostart.add_argument('app', help='Application name') + +parser_disable_autostart = subparsers.add_parser('disable-autostart', help='Disables application autostart') +parser_disable_autostart.set_defaults(action='disable-autostart') +parser_disable_autostart.add_argument('app', help='Application name') + +parser_register_proxy = subparsers.add_parser('register-proxy', help='Rebuilds nginx proxy target for an application container') +parser_register_proxy.set_defaults(action='register-proxy') +parser_register_proxy.add_argument('app', help='Application name') + +parser_unregister_proxy = subparsers.add_parser('unregister-proxy', help='Removes nginx proxy target for an application container') +parser_unregister_proxy.set_defaults(action='unregister-proxy') +parser_unregister_proxy.add_argument('app', help='Application name') + +parser_update_host = subparsers.add_parser('update-host', help='Rebuilds domain structure of VM with new host name and new HTTPS port') +parser_update_host.set_defaults(action='update-host') +parser_update_host.add_argument('domain', help='Domain name') +parser_update_host.add_argument('port', help='HTTPS port') + +parser_update_common = subparsers.add_parser('update-common', help='Updates common configuration properties used by multiple applications') +parser_update_common.set_defaults(action='update-common') +parser_update_common.add_argument('--email', help='Administrative e-mail address') +parser_update_common.add_argument('--gmaps-api-key', help='Google Maps API key') + +parser_request_cert = subparsers.add_parser('request-cert', help='Requests and installs Let\'s Encrypt certificate for currently set domain') +parser_request_cert.set_defaults(action='request-cert') + +parser_install_cert = subparsers.add_parser('install-cert', help='Installs user supplied certificate') +parser_install_cert.set_defaults(action='install-cert') +parser_install_cert.add_argument('certificate', help='Certificate file') +parser_install_cert.add_argument('key', help='Key file') + +args = parser.parse_args() +mgr = AppMgr() +if args.action == 'update-login': + mgr.update_login(args.app, args.login, args.password) +elif args.action == 'show-tiles': + mgr.show_tiles(args.app) +elif args.action == 'hide-tiles': + mgr.hide_tiles(args.app) +elif args.action == 'start-app': + mgr.start_app(args.app) +elif args.action == 'stop-app': + mgr.stop_app(args.app) +elif args.action == 'enable-autostart': + mgr.enable_autostart(args.app) +elif args.action == 'disable-autostart': + mgr.disable_autostart(args.app) +elif args.action == 'register-proxy': + mgr.register_proxy(args.app) +elif args.action == 'unregister-proxy': + mgr.unregister_proxy(args.app) +elif args.action == 'update-host': + mgr.update_host(args.domain, args.port) +elif args.action == 'update-common': + mgr.update_common(args.email, args.gmaps_api_key) +elif args.action == 'request-cert': + mgr.request_cert() +elif args.action == 'install-cert': + mgr.install_cert(args.certificate, args.key) diff --git a/basic/srv/spotter/config.json b/basic/srv/spotter/config.json new file mode 100644 index 0000000..a16de79 --- /dev/null +++ b/basic/srv/spotter/config.json @@ -0,0 +1,138 @@ +{ + "apps": { + "ckan": { + "host": "ckan", + "login": "N/A", + "password": "N/A", + "title": "CKAN", + "visible": false + }, + "crisiscleanup": { + "host": "cc", + "login": "N/A", + "password": "N/A", + "title": "Crisis Cleanup", + "visible": false + }, + "cts": { + "host": "cts", + "login": "N/A", + "password": "N/A", + "title": "CTS", + "visible": false + }, + "frontlinesms": { + "host": "sms", + "login": "N/A", + "password": "N/A", + "title": "Frontline SMS", + "visible": false + }, + "gnuhealth": { + "host": "gh", + "login": "N/A", + "password": "N/A", + "title": "GNU Health", + "visible": false + }, + "kanboard": { + "host": "kb", + "login": "N/A", + "password": "N/A", + "title": "KanBoard", + "visible": false + }, + "mifosx": { + "host": "mifosx", + "login": "N/A", + "password": "N/A", + "title": "Mifos X", + "visible": false + }, + "motech": { + "host": "motech", + "login": "N/A", + "password": "N/A", + "title": "Motech", + "visible": false + }, + "opendatakit": { + "host": "odk", + "login": "N/A", + "password": "N/A", + "title": "OpenDataKit Aggregate", + "visible": false + }, + "opendatakit-build": { + "host": "odkbuild", + "login": "N/A", + "password": "N/A", + "title": "OpenDataKit Build", + "visible": false + }, + "openmapkit": { + "host": "omk", + "login": "N/A", + "password": "N/A", + "title": "OpenMapKit", + "visible": false + }, + "pandora": { + "host": "pandora", + "login": "N/A", + "password": "N/A", + "title": "Pan.do/ra", + "visible": false + }, + "sahana": { + "host": "sahana", + "login": "N/A", + "password": "N/A", + "title": "Sahana EDEN", + "visible": false + }, + "sahana-demo": { + "host": "sahana-demo", + "login": "N/A", + "password": "N/A", + "title": "Sahana EDEN Demo", + "visible": false + }, + "sambro": { + "host": "sambro", + "login": "N/A", + "password": "N/A", + "title": "Sahana EDEN SAMBRO", + "visible": false + }, + "seeddms": { + "host": "dms", + "login": "N/A", + "password": "N/A", + "title": "SeedDMS", + "visible": false + }, + "sigmah": { + "host": "sigmah", + "login": "N/A", + "password": "N/A", + "title": "Sigmah", + "visible": false + }, + "ushahidi": { + "host": "ush", + "login": "N/A", + "password": "N/A", + "title": "Ushahidi", + "visible": false + } + }, + "common": { + "email": "admin@example.com", + "gmaps-api-key": "" + }, + "host": { + "domain": "spotter.vm", + "port": "443" + } +} diff --git a/basic/srv/portal/error.html b/basic/srv/spotter/error.html similarity index 100% rename from basic/srv/portal/error.html rename to basic/srv/spotter/error.html diff --git a/basic/srv/spotter/static/css/style.css b/basic/srv/spotter/static/css/style.css new file mode 100644 index 0000000..2b307a6 --- /dev/null +++ b/basic/srv/spotter/static/css/style.css @@ -0,0 +1,172 @@ +body { + font-family: 'Calibri', 'Verdana', 'Tahoma', sans-serif; + background-color: #bbb; + color: #000; + line-height: 150%; + margin: 25px 30px; +} + +a { + color: #06f; + text-decoration: none; +} + +img { + border: 0px; +} + +nav { + float: right; + margin-right: 30px; +} + +nav a { + display: block; + color: #00c; +} + +h1, h2 { + font-size: 150%; +} + +header { + color: #fff; +} + +header h1, +header p, +.portal-box p { + padding: 0px; + margin: 0px; +} + +.portal-box, +.setup-box { + background-color: #fff; + margin-top: 13px; + margin-right: 13px; + border: solid 1px #000; + padding: 10px; +} + +.portal-box { + position: relative; + width: 365px; + float: left; + height: 175px; +} + +.portal-box h2 { + margin: 0px; + font-weight: normal; +} + +.portal-box h2 a { + color: inherit; +} + +.portal-box h2 img { + float: right; + margin-left: 10px; + margin-bottom:10px; + width: 100px; + height: 100px; +} + +.portal-box ul { + margin: 0px; + padding-left: 30px; +} + +.portal-box:last-child:after { + clear: both; +} + +.portal-box-double-width { + width: 765px; +} + +.ico { + margin-right: 5px; + width: 20px; + height: 20px; + vertical-align: top; +} + +.setup-box h2 { + margin-top: 0px; +} + +.setup-box input[type="text"], +.setup-box input[type="submit"], +.setup-box input[type="button"], +.setup-box input[type="file"], +.setup-box select { + box-sizing: border-box; + width: 180px; +} + +.setup-box table { + border-collapse: collapse; +} + +.setup-box thead { + font-weight: bold; +} + +.setup-box td { + padding: 1px 10px; + vertical-align: top; +} + +.setup-box td:first-child { + white-space: nowrap; +} + +.setup-box td.remark { + color: #999; + font-size: 80%; + font-style: italic; + line-height: 125%; + padding-top: 5px; +} + +.center { + text-align: center; +} + +.error { + color: #c00; + font-weight: bold; +} + +.info { + color: #090; + font-weight: bold; +} + +.loader-wrap { + display: none; +} + +.loader-wrap span:after { + clear: both; + content: ''; + display: table; +} + +.loader { + float: left; + width: 14px; + height: 14px; + border: 5px solid #eee; + border-top: 5px solid #fa3; + border-radius: 50%; + margin-right: 5px; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/basic/srv/portal/img/CAP.png b/basic/srv/spotter/static/img/CAP.png similarity index 100% rename from basic/srv/portal/img/CAP.png rename to basic/srv/spotter/static/img/CAP.png diff --git a/basic/srv/portal/img/CKAN.png b/basic/srv/spotter/static/img/CKAN.png similarity index 100% rename from basic/srv/portal/img/CKAN.png rename to basic/srv/spotter/static/img/CKAN.png diff --git a/basic/srv/portal/img/CTS.png b/basic/srv/spotter/static/img/CTS.png similarity index 100% rename from basic/srv/portal/img/CTS.png rename to basic/srv/spotter/static/img/CTS.png diff --git a/basic/srv/portal/img/cluster_spotter.png b/basic/srv/spotter/static/img/Cluster_Spotter.png similarity index 100% rename from basic/srv/portal/img/cluster_spotter.png rename to basic/srv/spotter/static/img/Cluster_Spotter.png diff --git a/basic/srv/portal/img/Crisis_Cleanup.png b/basic/srv/spotter/static/img/Crisis_Cleanup.png similarity index 100% rename from basic/srv/portal/img/Crisis_Cleanup.png rename to basic/srv/spotter/static/img/Crisis_Cleanup.png diff --git a/basic/srv/portal/img/Diaspora.png b/basic/srv/spotter/static/img/Diaspora.png similarity index 100% rename from basic/srv/portal/img/Diaspora.png rename to basic/srv/spotter/static/img/Diaspora.png diff --git a/basic/srv/portal/img/EDEN.png b/basic/srv/spotter/static/img/EDEN.png similarity index 100% rename from basic/srv/portal/img/EDEN.png rename to basic/srv/spotter/static/img/EDEN.png diff --git a/basic/srv/portal/img/FrontlineSMS.png b/basic/srv/spotter/static/img/FrontlineSMS.png similarity index 100% rename from basic/srv/portal/img/FrontlineSMS.png rename to basic/srv/spotter/static/img/FrontlineSMS.png diff --git a/basic/srv/portal/img/GNU_Health.png b/basic/srv/spotter/static/img/GNU_Health.png similarity index 100% rename from basic/srv/portal/img/GNU_Health.png rename to basic/srv/spotter/static/img/GNU_Health.png diff --git a/basic/srv/portal/img/GeoODK_Collect.png b/basic/srv/spotter/static/img/GeoODK_Collect.png similarity index 100% rename from basic/srv/portal/img/GeoODK_Collect.png rename to basic/srv/spotter/static/img/GeoODK_Collect.png diff --git a/basic/srv/portal/img/Kanboard.png b/basic/srv/spotter/static/img/Kanboard.png similarity index 100% rename from basic/srv/portal/img/Kanboard.png rename to basic/srv/spotter/static/img/Kanboard.png diff --git a/basic/srv/portal/img/MifosX.png b/basic/srv/spotter/static/img/MifosX.png similarity index 100% rename from basic/srv/portal/img/MifosX.png rename to basic/srv/spotter/static/img/MifosX.png diff --git a/basic/srv/portal/img/MifosX_Mobile.png b/basic/srv/spotter/static/img/MifosX_Mobile.png similarity index 100% rename from basic/srv/portal/img/MifosX_Mobile.png rename to basic/srv/spotter/static/img/MifosX_Mobile.png diff --git a/basic/srv/portal/img/Motech.png b/basic/srv/spotter/static/img/Motech.png similarity index 100% rename from basic/srv/portal/img/Motech.png rename to basic/srv/spotter/static/img/Motech.png diff --git a/basic/srv/portal/img/ODK.png b/basic/srv/spotter/static/img/ODK.png similarity index 100% rename from basic/srv/portal/img/ODK.png rename to basic/srv/spotter/static/img/ODK.png diff --git a/basic/srv/portal/img/ODK_Collect.png b/basic/srv/spotter/static/img/ODK_Collect.png similarity index 100% rename from basic/srv/portal/img/ODK_Collect.png rename to basic/srv/spotter/static/img/ODK_Collect.png diff --git a/basic/srv/portal/img/OMK.png b/basic/srv/spotter/static/img/OMK.png similarity index 100% rename from basic/srv/portal/img/OMK.png rename to basic/srv/spotter/static/img/OMK.png diff --git a/basic/srv/portal/img/OpenID.png b/basic/srv/spotter/static/img/OpenID.png similarity index 100% rename from basic/srv/portal/img/OpenID.png rename to basic/srv/spotter/static/img/OpenID.png diff --git a/basic/srv/portal/img/POSM.png b/basic/srv/spotter/static/img/POSM.png similarity index 100% rename from basic/srv/portal/img/POSM.png rename to basic/srv/spotter/static/img/POSM.png diff --git a/basic/srv/portal/img/Pandora.png b/basic/srv/spotter/static/img/Pandora.png similarity index 100% rename from basic/srv/portal/img/Pandora.png rename to basic/srv/spotter/static/img/Pandora.png diff --git a/basic/srv/portal/img/PostGIS.png b/basic/srv/spotter/static/img/PostGIS.png similarity index 100% rename from basic/srv/portal/img/PostGIS.png rename to basic/srv/spotter/static/img/PostGIS.png diff --git a/basic/srv/portal/img/SMS_Sync.png b/basic/srv/spotter/static/img/SMS_Sync.png similarity index 100% rename from basic/srv/portal/img/SMS_Sync.png rename to basic/srv/spotter/static/img/SMS_Sync.png diff --git a/basic/srv/portal/img/SeedDMS.png b/basic/srv/spotter/static/img/SeedDMS.png similarity index 100% rename from basic/srv/portal/img/SeedDMS.png rename to basic/srv/spotter/static/img/SeedDMS.png diff --git a/basic/srv/portal/img/Sigmah.png b/basic/srv/spotter/static/img/Sigmah.png similarity index 100% rename from basic/srv/portal/img/Sigmah.png rename to basic/srv/spotter/static/img/Sigmah.png diff --git a/basic/srv/portal/img/Ushahidi.png b/basic/srv/spotter/static/img/Ushahidi.png similarity index 100% rename from basic/srv/portal/img/Ushahidi.png rename to basic/srv/spotter/static/img/Ushahidi.png diff --git a/basic/srv/portal/img/Ushahidi_mobile.png b/basic/srv/spotter/static/img/Ushahidi_mobile.png similarity index 100% rename from basic/srv/portal/img/Ushahidi_mobile.png rename to basic/srv/spotter/static/img/Ushahidi_mobile.png diff --git a/basic/srv/portal/img/android.png b/basic/srv/spotter/static/img/icons/Android.png similarity index 100% rename from basic/srv/portal/img/android.png rename to basic/srv/spotter/static/img/icons/Android.png diff --git a/basic/srv/portal/img/java.png b/basic/srv/spotter/static/img/icons/Java.png similarity index 100% rename from basic/srv/portal/img/java.png rename to basic/srv/spotter/static/img/icons/Java.png diff --git a/basic/srv/portal/img/Linux.png b/basic/srv/spotter/static/img/icons/Linux.png similarity index 100% rename from basic/srv/portal/img/Linux.png rename to basic/srv/spotter/static/img/icons/Linux.png diff --git a/basic/srv/portal/img/MacOS.png b/basic/srv/spotter/static/img/icons/MacOS.png similarity index 100% rename from basic/srv/portal/img/MacOS.png rename to basic/srv/spotter/static/img/icons/MacOS.png diff --git a/basic/srv/portal/img/Windows.png b/basic/srv/spotter/static/img/icons/Windows.png similarity index 100% rename from basic/srv/portal/img/Windows.png rename to basic/srv/spotter/static/img/icons/Windows.png diff --git a/basic/srv/portal/img/ios.png b/basic/srv/spotter/static/img/icons/iOS.png similarity index 100% rename from basic/srv/portal/img/ios.png rename to basic/srv/spotter/static/img/icons/iOS.png diff --git a/basic/srv/portal/js/jquery-3.3.1.min.js b/basic/srv/spotter/static/js/jquery-3.3.1.min.js similarity index 100% rename from basic/srv/portal/js/jquery-3.3.1.min.js rename to basic/srv/spotter/static/js/jquery-3.3.1.min.js diff --git a/basic/srv/spotter/static/js/script.js b/basic/srv/spotter/static/js/script.js new file mode 100644 index 0000000..13625c9 --- /dev/null +++ b/basic/srv/spotter/static/js/script.js @@ -0,0 +1,163 @@ +$(function() { + $('#update-host').on('submit', update_host); + $('#verify-dns').on('click', verify_dns); + $('#verify-https').on('click', verify_https); + $('#verify-http').on('click', verify_http); + $('#cert-method').on('change', toggle_cert_method); + $('#update-cert').on('submit', update_cert); + $('#update-common').on('submit', update_common); + $('.app-visible').on('click', update_app_visibility); + $('.app-autostart').on('click', update_app_autostart); + $('tr[data-app]').on('click', '.app-start', start_app).on('click', '.app-stop', stop_app); +}); + +function update_host() { + $('#host-submit').hide(); + $('#host-message').hide(); + $('#host-wait').show(); + $.post('/update-host', {'domain': $('#domain').val(), 'port': $('#port').val()}, function(data) { + $('#host-wait').hide(); + if (data.error) { + $('#host-message').attr('class','error').html(data.error).show(); + $('#host-submit').show(); + } else { + $('#host-message').attr('class','info').html(data.ok).show(); + } + }); + return false; +} + +function verify_dns() { + $('#verify-dns').hide(); + $('#dns-message').hide(); + $('#dns-wait').show(); + $.get('/verify-dns', function(data) { + $('#dns-wait').hide(); + if (data.error) { + $('#dns-message').attr('class','error').html(data.error).show(); + $('#verify-dns').show(); + } else { + $('#dns-message').attr('class','info').html(data.ok).show(); + } + }); + return false; +} + +function _verify_http(proto) { + $('#verify-'+proto).hide(); + $('#'+proto+'-message').hide(); + $('#'+proto+'-wait').show(); + $.get('/verify-' + proto, function(data) { + $('#'+proto+'-wait').hide(); + if (data.error) { + $('#'+proto+'-message').attr('class','error').html(data.error).show(); + $('#verify-'+proto).show(); + } else { + $('#'+proto+'-message').attr('class','info').html(data.ok).show(); + } + }); + return false; +} + +function verify_http() { + return _verify_http('http'); +} + +function verify_https() { + return _verify_http('https'); +} + +function toggle_cert_method() { + if ($('#cert-method').val() == 'manual') { + $('.cert-upload').show(); + } else { + $('.cert-upload').hide(); + } +} + +function update_cert() { + $('#cert-submit').hide(); + $('#cert-message').hide(); + $('#cert-wait').show(); + $.ajax({url: '/update-cert', type: 'POST', data: new FormData($('#update-cert')[0]), cache: false, contentType: false, processData: false, success: function(data) { + $('#cert-wait').hide(); + if (data.error) { + $('#cert-message').attr('class','error').text(data.error).show(); + $('#cert-submit').show(); + } else { + $('#cert-message').attr('class','info').text(data.ok).show(); + } + }}); + return false; +} + +function update_common() { + $('#common-submit').hide(); + $('#common-message').hide(); + $('#common-wait').show(); + $.post('/update-common', {'email': $('#email').val(), 'gmaps-api-key': $('#gmaps-api-key').val()}, function(data) { + $('#common-wait').hide(); + if (data.error) { + $('#common-message').attr('class','error').html(data.error).show(); + $('#common-submit').show(); + } else { + $('#common-message').attr('class','info').html(data.ok).show(); + $('#common-submit').show(); + } + }); + return false; +} + +function update_app_visibility(ev) { + var el = $(ev.target); + var app = el.closest('tr').data('app'); + var value = el.is(':checked') ? 'true' : ''; + $.post('/update-app-visibility', {'app': app, 'value': value}, function(data) { + if (data.error) { + el.prop('checked', !value); + alert(data.error); + } + }); +} + +function update_app_autostart(ev) { + var el = $(ev.target); + var app = el.closest('tr').data('app'); + var value = el.is(':checked') ? 'true' : ''; + $.post('/update-app-autostart', {'app': app, 'value': value}, function(data) { + if (data.error) { + el.prop('checked', !value); + alert(data.error); + } + }); +} + +function start_app(ev) { + var el = $(ev.target); + var app = el.closest('tr').data('app'); + var td = el.closest('td'); + td.html('
'); + $.post('/start-app', {'app': app}, function(data) { + if (data.error) { + td.attr('class','error').html(data.error); + } else { + td.removeAttr('class').html(data.ok); + } + }); + return false; +} + +function stop_app(ev) { + var el = $(ev.target); + var app = el.closest('tr').data('app'); + var td = el.closest('td'); + td.html('
'); + $.post('/stop-app', {'app': app}, function(data) { + if (data.error) { + td.attr('class','error').html(data.error); + } else { + td.removeAttr('class').html(data.ok); + } + }); + return false; +} diff --git a/basic/srv/spotter/templates/layout.html b/basic/srv/spotter/templates/layout.html new file mode 100644 index 0000000..3a2b61b --- /dev/null +++ b/basic/srv/spotter/templates/layout.html @@ -0,0 +1,28 @@ + + + + + + + + + {% block title %}{% endblock %} + + + + + + + +
+

CLUSTER NGO

+

Sada softwarových nástrojů určená pro krizový management.

+
+ {% block body %}{% endblock %} + + diff --git a/basic/srv/spotter/templates/portal.html b/basic/srv/spotter/templates/portal.html new file mode 100644 index 0000000..3fdc738 --- /dev/null +++ b/basic/srv/spotter/templates/portal.html @@ -0,0 +1,345 @@ +{% extends 'layout.html' %} +{% block title %}Cluster NGO{% endblock %} +{% block body %} +{% set host = '{}:{}'.format(conf['host']['domain'], conf['host']['port']) if conf['host']['port'] != '443' else conf['host']['domain'] %} +{% set app = conf['apps']['sahana'] %} +{% if app['visible'] %} +
+

Sahana EDENSahana EDEN

+

Registr kontaktů asociací, organizací, jednotek zaměstnanců, dobrovolníků, Registr prostředků, materiálních zdrojů určených pro činnost v krizových situacích, logistika krizového zboží ve skladištích, úkrytech, organizace lidských zdrojů, diobrovolníků, mapová vizualizace pro lokalizaci a popis krizové události a mnoho dalších funkcí.

+ +
+{% endif %} + +{% set app = conf['apps']['sahana-demo'] %} +{% if app['visible'] %} +
+

Sahana EDEN DEMOSahana EDEN DEMO

+

Přístup určený k bezpečnému vyzkoušení aplikace. Zde můžete přidávat i mazat testovací data.

+ +
+{% endif %} + +{% set app = conf['apps']['sambro'] %} +{% if app['visible'] %} +
+

Sahana EDEN SAMBROSahana EDEN SAMBRO

+

Samostatná instance Sahana EDEN s šablonou SAMBRO.

+ +
+ +
+

SAMBRO MobileSAMBRO Mobile

+

Mobilní klient k aplikaci Sahana EDEN.
+ IOSIOS 6.0 a vyšší
+ AndroidAndroid 4.0 a vyšší +

+ +
+{% endif %} + +{% set app = conf['apps']['crisiscleanup'] %} +{% if app['visible'] %} +
+

Crisis CleanupCrisis Cleanup

+

Mapování krizové pomoci při odstraňování následků katastrof a koordinaci práce. Jde o majetek, ne o lidi.

+ +
+{% endif %} + +{% set app = conf['apps']['ckan'] %} +{% if app['visible'] %} +
+

CKANCKAN

+

Repository management a datová analýza pro vytváření otevřených dat.

+ +
+{% endif %} + +{% set app = conf['apps']['opendatakit-build'] %} +{% if app['visible'] %} +
+

Open Data KitODK Build

+

Sběr dat s pomocí smartphone.
Aplikace pro návrh formulářů
+

XLSForm - online konverter XLS.
+ ODK Form UploaderODK Form Uploader
+ ODK ValidateODK Validate

+
+{% endif %} + +{% set app = conf['apps']['opendatakit'] %} +{% if app['visible'] %} +
+

Open Data KitODK Collect

+

Mobilní aplikace
+ ODK CollectODK Collect pro Android
+ ODK BriefcaseODK Briefcase
+

+ +
+ +
+

Open Data KitODK Aggregate

+

Sběr dat s pomocí smartphone.
+ GeoODK Collect - náhrada papírových dotazníků smartphonem. +

+ +
+{% endif %} + +{% set app = conf['apps']['openmapkit'] %} +{% if app['visible'] %} +
+

Open Map KitOpenMapKit Server

+

Sběr dat s pomocí smartphone.
+

+
+ +
+

GeoODK CollectGeoODK Collect

+

Mobilní aplikace
+ GeoODK CollectGeoODK Collect pro Android +

+ +
+ +
+

Open Map KitOpenMapKit

+

Mobilní aplikace
+ ODK CollectODK Collect pro Android
+ AndroidOpenMapKit pro Android 4.1 a vyšší +

+ +
+{% endif %} + +{% set app = conf['apps']['frontlinesms'] %} +{% if app['visible'] %} +
+

FrontlineSMSFrontlineSMS

+

SMS messaging přes veřejné datové brány

+ +
+ +
+

SMS Sync GatewaySMS Sync Gateway

+

Mobilní aplikace pro
+ AndroidAndroid 2.3 a vyšší +

+
+{% endif %} + +{% set app = conf['apps']['seeddms'] %} +{% if app['visible'] %} +
+

SeedDMSSeedDMS

+

Dokument management na dokumentaci a projektovou dokumentaci

+ +
+{% endif %} + +{% set app = conf['apps']['pandora'] %} +{% if app['visible'] %} +
+

Pan.do/raPan.do/ra

+

Media management na foto a video z krizové události. Tvorba metadat, komentářů, lokalizace v čase a na mapě.

+ +
+{% endif %} + +{% set app = conf['apps']['ushahidi'] %} +{% if app['visible'] %} +
+

UshahidiUshahidi

+

Reakce na krizovou událost. Shromažďujte zprávy od obětí a pracovníků v terénu prostřednictvím SMS, e-mailu, webu, Twitteru.

+ +
+ +
+

UshahidiUshahidi Mobile

+

Mobilní aplikace Ushahidi pro
+ IOSIOS 9.0 a vyšší
+ AndroidAndroid 4.4 a vyšší +

+ +
+{% endif %} + +{% set app = conf['apps']['kanboard'] %} +{% if app['visible'] %} +
+

KanboardKanboard

+

Usnadňuje tvorbu a řízení projektů s pomocí Kanban metodiky.

+ +
+ +
+

KanboardKanboard Mobile

+

Mobilní aplikace
+ KanBoardKanBoard client pro Android 4.1 a vyšší
+ AndroidKandroid pro Android 4.2 a vyšší +

+ +
+{% endif %} + +{% set app = conf['apps']['cts'] %} +{% if app['visible'] %} +
+

CTSCTS

+

Logistika hmotné pomoci pro humanitární potřeby.

+ +
+{% endif %} + +{% set app = conf['apps']['gnuhealth'] %} +{% if app['visible'] %} +
+

GNU HealthGNU Health

+

Zdravotní a nemocniční informační systém.

+ +
+ +
+

GNU HealthGNU Health klienti

+

Klientské aplikace platformy Tryton GNU Health pro
+ WindowsWindows
+ MacOSMacOS
+ LinuxLinux +

+ +
+{% endif %} + +{% set app = conf['apps']['sigmah'] %} +{% if app['visible'] %} +
+

SigmahSigmah

+

Rozpočtování získávání finančních prostředků.

+ +
+{% endif %} + +{% set app = conf['apps']['motech'] %} +{% if app['visible'] %} +
+

MotechMotech

+

Integrace zdravotnických a komunikačních služeb.

+ +
+{% endif %} + +{% set app = conf['apps']['mifosx'] %} +{% if app['visible'] %} +
+

Mifos XMifos X

+

Nástroj na rozvojovou, humanitární pomoc a mikrofinancování.

+ +
+ +
+

Mifos XMifos X

+

Mobilní aplikace
+ Mifos XMifos X client pro Android 3.0 a vyšší
+

+ +
+{% endif %} + +{% if false %} +
+

diaspora*diaspora*

+

Autonomní sociání síť s možností propojení do cizích sociálních sítí.

+ +
+ +
+

OpenIDOpenID

+

Pro ověření identity budete potřebovat účet OpenID. Zaregistrujte se. Registraci využijete v software Sahana EDEN.

+
+ +
+

POSMPOSM

+

Portable Open Street Map - softwarový balík na offline používání OpenStreet Map v samostatné virtuální image.

+
+{% endif %} + +
+

Cluster SpotterCluster Spotter

+

Info o Misi a Vizi projektu, včetně kontaktu. Zachovejte data bezpečná a neposkytujte je nepovolaným osobám.
+ CC 4.0 CZ by TS. Content is based on PD, CC, GNU/GPL. Brand names, trademarks belong to their respective holders. +

+
+{% endblock %} diff --git a/basic/srv/spotter/templates/setup-apps.html b/basic/srv/spotter/templates/setup-apps.html new file mode 100644 index 0000000..10cf7ba --- /dev/null +++ b/basic/srv/spotter/templates/setup-apps.html @@ -0,0 +1,58 @@ +{% extends 'layout.html' %} +{% block title %}Nastavení aplikací{% endblock %} +{% block body %} +
+

Nastavení aplikací

+

Společné nastavení sdílené některými aplikacemi.

+
+ + + + + + + + + + + + + + + +
E-mailAdministrativní e-mail na který budou doručovány zprávy a upozornění z aplikací. Stejná e-mailová adresa bude také využita některými aplikacemi pro odesílání zpráv uživatelům.
Google Maps API klíčAPI klíč pro službu Google Maps, která je využita některými aplikacemi.
  + +
+
+
+ Provádí se změna nastavení, prosím čekejte... +
+
+
+
+ +
+

Správce aplikací

+

Vyberte které aplikace se mají zobrazovat na hlavní straně portálu a které mají být automaticky spuštěny při startu virtuálního stroje.

+ + + + + + + + + + + {% for app in conf['apps']|sort %} + + + + + + + {% endfor %} + +
AplikaceZobrazenaAutostartStav
{{ conf['apps'][app]['title'] }}{% if is_service_started(app) %}Spuštěna (zastavit){% else %}Zastavena (spustit){% endif %}
+
+{% endblock %} diff --git a/basic/srv/spotter/templates/setup-host.html b/basic/srv/spotter/templates/setup-host.html new file mode 100644 index 0000000..b7795dd --- /dev/null +++ b/basic/srv/spotter/templates/setup-host.html @@ -0,0 +1,115 @@ +{% extends 'layout.html' %} +{% block title %}Nastavení hostitele{% endblock %} +{% block body %} +
+

HTTPS Hostitel

+

Základní doménové jméno a HTTPS port na kterých budou přístupny všechny aplikace.

+
+ + + + + + + + + + + + + + + +
DoménaPlně kvalifikovaný doménový název, na kterém bude dostupný aplikační portál. Jednotlivé aplikace budou dostupné na subdoménách této domény.
PortHTTPS port na kterém budou dostupné aplikace. Výchozí HTTPS port je 443.
  + +
+
+
+ Provádí se změna nastavení, prosím čekejte... +
+
+
+
+ +
+

DNS záznamy

+

Na jmenném serveru domény nastavené v sekci HTTPS Hostitel nastavte DNS záznamy typu A, případně i AAAA pro následující doménové názvy a nasměrujte je na vnější (tj. dostupnou z internetu) IP adresu tohoto virtuální stroje. Toto nastavení lze obvykle provést skrze webové rozhraní registrátora domény.

+

Vnější IPv4 {% if ex_ipv4 %}je {{ ex_ipv4 }}{% else %}nebyla zjištěna{% endif %} a IPv6 {% if ex_ipv6 %}je {{ ex_ipv6 }}{% else %}nebyla zjištěna{% endif %}.

+ +

Pokud jmenný server nepodporuje wildcard záznamy nebo pokud nemůžete či nechcete dedikovat virtuálnímu stroji všechny subdomény, nastavte místo toho záznamy pro následující doménové názvy

+ + +
+
+
+ Ověřuje se nastavení DNS, prosím čekejte... +
+
+ +
+

Firewall a NAT

+

Pokud je stávající připojení k internetu zprostředkováno routerem s NAT, na hypervizoru je nastaven firewall nebo existují jiné restrikce síťového provozu, je nutno upravit nastavení příslušných komponent, aby byl provoz na portu {{ conf['host']['port'] }} (nastaveném v sekci HTTPS Hostitel) z internetu korektně nasměrován na místní adresu virtuálního stroje.

+

Pokud bude využit systém automatického vyžádání a obnovy certifikátu (sekce HTTPS certifikát), je nutno aby byl na místní adresu virtuálního stroje nasměrován i port 80, případně byla nastavena HTTP proxy přesměrovávající doménová jména zmíněná v sekci DNS záznamy.

+

Místní IPv4 {% if in_ipv4 %}je {{ in_ipv4 }}{% else %}nebyla zjištěna{% endif %} a IPv6 {% if in_ipv6 %}je {{ in_ipv6 }}{% else %}nebyla zjištěna{% endif %}.

+ +
+
+
+ Ověřuje se nastavení firewallu a NAT pro port {{ conf['host']['port'] }}, prosím čekejte... +
+ +
+
+
+ Ověřuje se nastavení firewallu a NAT pro port 80, prosím čekejte... +
+
+ +
+

HTTPS certifikát

+

Stávající certifikát je vystaven na jméno {{ cert_info['subject']['commonName'] }} vystavitelem {{ cert_info['issuer']['commonName'] }} a jeho platnost vyprší {{ cert_info['notAfter'] }}.

+
+ + + + + + + + + + + + + + + + + + + + +
Způsob správy + + Volba "Automaticky" způsobí, že systém automaticky zažádá o certifikát certifikační autority Let's Encrypt pro všechny plně kvalifikované doménové názvy (tj. nikoliv wildcard) zmíněné v sekci DNS záznamy a nainstaluje úlohu pro jeho automatickou obnovu. Tato akce může trvat několik minut.
Volba "Ručně" znamená, že soubory certifikátu a jeho soukromého klíče je nutno nahrát a následně obnovovat ručně skrze formulář na této stránce.
  + +
+
+
+ Provádí se změna nastavení, prosím čekejte... +
+
+
+
+{% endblock %} diff --git a/basic/srv/spotter/wsgi.py b/basic/srv/spotter/wsgi.py new file mode 100755 index 0000000..8a34b03 --- /dev/null +++ b/basic/srv/spotter/wsgi.py @@ -0,0 +1,19 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import sys + +sys.path.append('/srv/spotter') +from appmgr.wsgiapp import WSGIApp + +application = WSGIApp() + +if __name__ == '__main__': + import os + from werkzeug.serving import run_simple + from werkzeug.wsgi import SharedDataMiddleware + + application = SharedDataMiddleware(application, { + '/static': os.path.join(os.path.dirname(__file__), 'static') + }) + run_simple('127.0.0.1', 8080, application, use_reloader=True) diff --git a/basic/usr/bin/spotter-appmgr b/basic/usr/bin/spotter-appmgr deleted file mode 100755 index 2717a45..0000000 --- a/basic/usr/bin/spotter-appmgr +++ /dev/null @@ -1,318 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- - -import argparse -import json -import os -import subprocess - -CONF_FILE = '/srv/config.json' -DISCARD_IP = '[100::1]' -ISSUE_FILE = '/etc/issue' -NGINX_DIR = '/etc/nginx/conf.d' - -NGINX_TEMPLATE = '''server {{ - listen [::]:{port} ssl http2; - server_name {host}.{domain}; - - access_log /var/log/nginx/{app}.access.log; - error_log /var/log/nginx/{app}.error.log; - - location / {{ - proxy_pass http://{ip}:8080; - }} - - error_page 502 /error.html; - location /error.html {{ - root /srv/portal; - }} -}} -''' - -NGINX_DEFAULT_TEMPLATE = '''server {{ - listen [::]:80 default_server ipv6only=off; - - location / {{ - return 301 https://$host:{port}$request_uri; - }} - location /.well-known/acme-challenge/ {{ - root /etc/acme.sh.d; - }} -}} - -server {{ - listen [::]:{port} ssl http2 default_server ipv6only=off; - root /srv/portal; - index index.html; - - location / {{ - try_files $uri $uri/ =404; - }} - location /config.json {{ - alias /srv/config.json; - }} -}} -''' - -ISSUE_TEMPLATE = ''' -\x1b[1;32m _____ _ _ __ ____ __ - / ____| | | | | \\\\ \\\\ / / \\\\/ | - | (___ _ __ ___ | |_| |_ ___ _ _\\\\ \\\\ / /| \\\\ / | - \\\\___ \\\\| '_ \\\\ / _ \\\\| __| __/ _ \\\\ '__\\\\ \\\\/ / | |\\\\/| | - ____) | |_) | (_) | |_| || __/ | \\\\ / | | | | - |_____/| .__/ \\\\___/ \\\\__|\\\\__\\\\___|_| \\\\/ |_| |_| - | | - |_|\x1b[0m - - - - - \x1b[1;33mUPOZORNĚNÍ:\x1b[0m Neoprávněný přístup k tomuto zařízení je zakázán. - Musíte mít výslovné oprávnění k přístupu nebo konfiguraci tohoto zařízení. - Neoprávněné pokusy a kroky k přístupu nebo používání tohoto systému mohou mít - za následek občanské nebo trestní sankce. - - - \x1b[1;33mCAUTION:\x1b[0m Unauthozired access to this device is prohibited. - You must have explicit, authorized permission to access or configure this - device. Unauthorized attempts and actions to access or use this system may - result in civil or criminal penalties. - - - - - Pro přístup k aplikacím otevřete URL \x1b[1mhttps://{host}\x1b[0m ve Vašem - internetovém prohlížeči. - - - - - - -\x1b[0;30m -''' - -class SpotterManager: - def __init__(self): - # Load JSON configuration - with open(CONF_FILE, 'r') as f: - self.conf = json.load(f) - self.domain = self.conf['host']['domain'] - self.port = self.conf['host']['port'] - - def save_conf(self): - # Save a sorted JSON configuration object with indentation - with open(CONF_FILE, 'w') as f: - json.dump(self.conf, f, sort_keys=True, indent=4) - - def update_login(self, app, login, password): - # Update login and password for an app in the configuration - if login is not None: - self.conf['apps'][app]['login'] = login - if password is not None: - self.conf['apps'][app]['password'] = password - self.save_conf() - - def show_tiles(self, app): - # Update tiles-shown for the app in the configuration - self.conf['apps'][app]['tiles-shown'] = True - self.save_conf() - - def hide_tiles(self, app): - # Update tiles-shown for the app in the configuration - self.conf['apps'][app]['tiles-shown'] = False - self.save_conf() - - def start_app(self, app): - # Start the actual app service - subprocess.call(['/sbin/service', app, 'start']) - - def stop_app(self, app): - # Stop the actual app service - subprocess.call(['/sbin/service', app, 'stop']) - # Stop the app service's dependencies if they are not used by another running app - deps = self.build_deps_tree() - for dep in self.get_app_deps(app): - if not any([self.is_app_started(d) for d in deps[dep]]): - subprocess.call(['/sbin/service', dep, 'stop']) - - def build_deps_tree(self): - # Fisrt, build a dictionary of {app: [needs]} - needs = {} - for app in self.conf['apps']: - needs[app] = self.get_app_deps(app) - # Then reverse it to {need: [apps]} - deps = {} - for app, need in needs.iteritems(): - for n in need: - deps.setdefault(n, []).append(app) - return deps - - def get_app_deps(self, app): - # Get "needs" line from init script and split it to list, skipping first two elements (docker, net) - try: - with open(os.path.join('/etc/init.d', app), 'r') as f: - return [l.split()[2:] for l in f.readlines() if l.startswith('\tneed')][0] - except: - return [] - - def is_app_started(self, app): - # Check OpenRC service status without calling any binary - return os.path.exists(os.path.join('/run/openrc/started', app)) - - def enable_autostart(self, app): - # Add the app to OpenRC default runlevel - subprocess.call(['/sbin/rc-update', 'add', app]) - - def disable_autostart(self, app): - # Remove the app from OpenRC default runlevel - subprocess.call(['/sbin/rc-update', 'del', app]) - - def register_proxy(self, app): - # Rebuild nginx configuration using IP of referenced app container and reload nginx - self.update_proxy_conf(app, self.get_container_ip(app)) - subprocess.call(['/sbin/service', 'nginx', 'reload']) - - def update_proxy_conf(self, app, ip): - with open(os.path.join(NGINX_DIR, '{}.conf'.format(app)), 'w') as f: - f.write(NGINX_TEMPLATE.format(app=app, host=self.conf['apps'][app]['host'], ip=ip, domain=self.domain, port=self.port)) - - def unregister_proxy(self, app): - # Remove nginx configuration to prevent proxy mismatch when the container IP is reassigned to another container - self.update_proxy_conf(app, DISCARD_IP) - subprocess.call(['/sbin/service', 'nginx', 'reload']) - - def get_container_ip(self, app): - # Return an IP address of a container. If the container is not running, return address from IPv6 discard prefix instead - try: - return subprocess.check_output(['/usr/bin/docker', 'inspect', '-f', '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}', app]).strip() - except: - return DISCARD_IP - - def update_domain(self, domain, port): - self.domain = self.conf['host']['domain'] = domain - self.port = self.conf['host']['port'] = port - self.save_conf() - self.rebuild_nginx() - self.rebuild_issue() - self.restart_apps() - - def rebuild_nginx(self): - # Rebuild nginx config for the portal app - with open(os.path.join(NGINX_DIR, 'default.conf'), 'w') as f: - f.write(NGINX_DEFAULT_TEMPLATE.format(port=self.port)) - # Unregister nginx proxy for apps (will be repopulated on app restart) - for app in self.conf['apps']: - self.update_proxy_conf(app, DISCARD_IP) - # Restart nginx to properly bind the new listen port - subprocess.call(['/sbin/service', 'nginx', 'restart']) - - def rebuild_issue(self): - # Compile the HTTPS host displayed in terminal banner - host = self.domain - # If the dummy host is used, take an IP address of a primary interface instead - if self.domain == 'spotter.vm': - host = subprocess.check_output(['/sbin/ip', 'route', 'get', '1']).split()[-1] - # Show port number only when using the non-default HTTPS port - if self.port != '443': - host += ':{}'.format(self.port) - # Rebuild the terminal banner - with open(ISSUE_FILE, 'w') as f: - f.write(ISSUE_TEMPLATE.format(host=host)) - - def restart_apps(self): - for app in self.conf['apps']: - # Check if a script for internal update of URL in the app exists and is executable and run it - script_path = os.path.join('/srv', app, 'update-url.sh') - if os.path.exists(script_path) and os.access(script_path, os.X_OK): - subprocess.call([script_path, '{}.{}'.format(self.conf['apps'][app]['host'], self.domain), self.port]) - # If the app is currently running, restart the app service - if self.is_app_started(app): - subprocess.call(['/sbin/service', app, 'restart']) - - def request_cert(self, email): - # Compile an acme.sh command for certificate requisition - cmd = ['/usr/bin/acme.sh', '--issue', '-d', self.domain] - for app in self.conf['apps']: - cmd += ['-d', '{}.{}'.format(self.conf['apps'][app]['host'], self.domain)] - cmd += ['-w', '/etc/acme.sh.d', '--accountemail', email] - # Request the certificate. If the requisition command fails, CalledProcessError will be raised - subprocess.check_output(cmd, stderr=subprocess.STDOUT) - # Install the issued certificate - subprocess.call(['/usr/bin/acme.sh', '--installcert', '-d', self.domain, '--keypath', '/etc/ssl/private/services.key', '--fullchainpath', '/etc/ssl/certs/services.pem', '--reloadcmd', 'service nginx reload']) - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Spotter VM application manager') - subparsers = parser.add_subparsers() - - parser_update_login = subparsers.add_parser('update-login', help='Updates application login') - parser_update_login.set_defaults(action='update-login') - parser_update_login.add_argument('app', help='Application name') - parser_update_login.add_argument('login', help='Administrative login') - parser_update_login.add_argument('password', help='Administrative password') - - parser_show_tiles = subparsers.add_parser('show-tiles', help='Shows application tiles in Portal') - parser_show_tiles.set_defaults(action='show-tiles') - parser_show_tiles.add_argument('app', help='Application name') - - parser_hide_tiles = subparsers.add_parser('hide-tiles', help='Hides application tiles in Portal') - parser_hide_tiles.set_defaults(action='hide-tiles') - parser_hide_tiles.add_argument('app', help='Application name') - - parser_start_app = subparsers.add_parser('start-app', help='Start application including it\'s dependencies') - parser_start_app.set_defaults(action='start-app') - parser_start_app.add_argument('app', help='Application name') - - parser_stop_app = subparsers.add_parser('stop-app', help='Stops application including it\'s dependencies if they are not used by another running application') - parser_stop_app.set_defaults(action='stop-app') - parser_stop_app.add_argument('app', help='Application name') - - parser_enable_autostart = subparsers.add_parser('enable-autostart', help='Enables application autostart') - parser_enable_autostart.set_defaults(action='enable-autostart') - parser_enable_autostart.add_argument('app', help='Application name') - - parser_disable_autostart = subparsers.add_parser('disable-autostart', help='Disables application autostart') - parser_disable_autostart.set_defaults(action='disable-autostart') - parser_disable_autostart.add_argument('app', help='Application name') - - parser_register_proxy = subparsers.add_parser('register-proxy', help='Rebuilds nginx proxy target for an application container') - parser_register_proxy.set_defaults(action='register-proxy') - parser_register_proxy.add_argument('app', help='Application name') - - parser_unregister_proxy = subparsers.add_parser('unregister-proxy', help='Removes nginx proxy target for an application container') - parser_unregister_proxy.set_defaults(action='unregister-proxy') - parser_unregister_proxy.add_argument('app', help='Application name') - - parser_update_domain = subparsers.add_parser('update-domain', help='Rebuilds domain structure of VM with new domain name and new HTTPS port') - parser_update_domain.set_defaults(action='update-domain') - parser_update_domain.add_argument('domain', help='Domain name') - parser_update_domain.add_argument('port', help='HTTPS port') - - parser_request_cert = subparsers.add_parser('request-cert', help='Requests and installs Let\'s Encrypt certificate for currently set domain') - parser_request_cert.set_defaults(action='request-cert') - parser_request_cert.add_argument('email', help='Email address to receive certificate notifications') - - args = parser.parse_args() - sm = SpotterManager() - if args.action == 'update-login': - sm.update_login(args.app, args.login, args.password) - elif args.action == 'show-tiles': - sm.show_tiles(args.app) - elif args.action == 'hide-tiles': - sm.hide_tiles(args.app) - elif args.action == 'start-app': - sm.start_app(args.app) - elif args.action == 'stop-app': - sm.stop_app(args.app) - elif args.action == 'enable-autostart': - sm.enable_autostart(args.app) - elif args.action == 'disable-autostart': - sm.disable_autostart(args.app) - elif args.action == 'register-proxy': - sm.register_proxy(args.app) - elif args.action == 'unregister-proxy': - sm.unregister_proxy(args.app) - elif args.action == 'update-domain': - sm.update_domain(args.domain, args.port) - elif args.action == 'request-cert': - sm.request_cert(args.email) diff --git a/ckan-datapusher/srv/ckan-datapusher/conf/datapusher_settings.py b/ckan-datapusher/srv/ckan-datapusher/conf/datapusher_settings.py index 93d8d5f..48b8b88 100644 --- a/ckan-datapusher/srv/ckan-datapusher/conf/datapusher_settings.py +++ b/ckan-datapusher/srv/ckan-datapusher/conf/datapusher_settings.py @@ -15,6 +15,6 @@ SQLALCHEMY_DATABASE_URI = 'sqlite:////srv/ckan-datapusher/data/jobs.db' HOST = '0.0.0.0' PORT = 8080 -FROM_EMAIL = 'ckan@spotter.ngo' +FROM_EMAIL = 'admin@example.com' STDERR = True diff --git a/ckan.sh b/ckan.sh index 17398cb..c3c2152 100755 --- a/ckan.sh +++ b/ckan.sh @@ -37,7 +37,6 @@ export CKAN_SECRET=$(head -c 18 /dev/urandom | base64) export CKAN_UUID=$(cat /proc/sys/kernel/random/uuid) envsubst <${SOURCE_DIR}/srv/ckan/conf/ckan.ini >/srv/ckan/conf/ckan.ini cp ${SOURCE_DIR}/srv/ckan/conf/who.ini /srv/ckan/conf/who.ini -cp ${SOURCE_DIR}/srv/ckan/update-url.sh /srv/ckan/update-url.sh # Set "production values" (increases performance) only if the DEBUG environment variable is not set if [ ${DEBUG:-0} -eq 0 ]; then diff --git a/ckan/srv/ckan/conf/ckan.ini b/ckan/srv/ckan/conf/ckan.ini index 8b0ca46..a95b4eb 100644 --- a/ckan/srv/ckan/conf/ckan.ini +++ b/ckan/srv/ckan/conf/ckan.ini @@ -116,7 +116,7 @@ ckan.views.default_views = image_view text_view recline_view geo_view geojson_vi # GeoView plugin settings ckanext.geoview.ol_viewer.formats = wms wfs geojson gml kml arcgis_rest gft -ckanext.geoview.gapi_key = AIzaSyBvIF3D550tlpL6o1xRrDurGo-81VhHlOw +ckanext.geoview.gapi_key = # Pages plugin settings ckanext.pages.organization = true @@ -203,7 +203,7 @@ smtp.server = postfix smtp.starttls = False #smtp.user = username@example.com #smtp.password = your_password -smtp.mail_from = ckan@spotter.ngo +smtp.mail_from = admin@example.com ## Logging configuration diff --git a/ckan/srv/ckan/update-url.sh b/ckan/srv/ckan/update-url.sh deleted file mode 100755 index 056f930..0000000 --- a/ckan/srv/ckan/update-url.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -HOST="${1}" -[ "${2}" != "443" ] && HOST="${1}:${2}" - -sed -i "s|^ckan\.site_url.*|ckan.site_url = https://${HOST}|" /srv/ckan/conf/ckan.ini diff --git a/crisiscleanup/srv/crisiscleanup/conf/boot.rb b/crisiscleanup/srv/crisiscleanup/conf/boot.rb index 0abb66f..60229ba 100644 --- a/crisiscleanup/srv/crisiscleanup/conf/boot.rb +++ b/crisiscleanup/srv/crisiscleanup/conf/boot.rb @@ -1,5 +1,5 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -ENV['GOOGLE_MAPS_API_KEY'] = 'AIzaSyBvIF3D550tlpL6o1xRrDurGo-81VhHlOw' +ENV['GOOGLE_MAPS_API_KEY'] = '' require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/crisiscleanup/srv/crisiscleanup/conf/initializers/devise.rb b/crisiscleanup/srv/crisiscleanup/conf/initializers/devise.rb index cd60e24..374208c 100644 --- a/crisiscleanup/srv/crisiscleanup/conf/initializers/devise.rb +++ b/crisiscleanup/srv/crisiscleanup/conf/initializers/devise.rb @@ -10,7 +10,7 @@ Devise.setup do |config| # Configure the e-mail address which will be shown in Devise::Mailer, # note that it will be overwritten if you use your own mailer class # with default "from" parameter. - config.mailer_sender = 'crisiscleanup@spotter.ngo' + config.mailer_sender = 'admin@example.com' # Configure the class responsible to send e-mails. # config.mailer = 'Devise::Mailer' diff --git a/cts/srv/cts/conf/spotter.py b/cts/srv/cts/conf/spotter.py index fdd9143..fe77949 100644 --- a/cts/srv/cts/conf/spotter.py +++ b/cts/srv/cts/conf/spotter.py @@ -35,7 +35,7 @@ SENDFILE_ROOT = os.path.join(PUBLIC_ROOT, 'static/protected') COMPRESS_ENABLED = False EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' -SERVER_EMAIL = 'cts@spotter.ngo' +SERVER_EMAIL = 'admin@example.com' CELERY_ALWAYS_EAGER = True CELERY_EAGER_PROPAGATES_EXCEPTIONS = True diff --git a/gnuhealth/srv/gnuhealth/conf/trytond.conf b/gnuhealth/srv/gnuhealth/conf/trytond.conf index be837b9..5aacc99 100644 --- a/gnuhealth/srv/gnuhealth/conf/trytond.conf +++ b/gnuhealth/srv/gnuhealth/conf/trytond.conf @@ -11,5 +11,5 @@ listen = *:8080 ssl_webdav = False [email] -from = gnuhealth@spotter.ngo +from = admin@example.com uri = smtp://postfix:25 diff --git a/kanboard/srv/kanboard/conf/config.php b/kanboard/srv/kanboard/conf/config.php index 5ef0d64..b3ae7e7 100644 --- a/kanboard/srv/kanboard/conf/config.php +++ b/kanboard/srv/kanboard/conf/config.php @@ -38,7 +38,7 @@ define('FILES_DIR', DATA_DIR.DIRECTORY_SEPARATOR.'files'); define('MAIL_CONFIGURATION', true); // E-mail address used for the "From" header (notifications) -define('MAIL_FROM', 'kanboard@spotter.ngo'); +define('MAIL_FROM', 'admin@example.com'); // Mail transport available: "smtp", "sendmail", "mail" (PHP mail function), "postmark", "mailgun", "sendgrid" define('MAIL_TRANSPORT', 'smtp'); diff --git a/mifosx.sh b/mifosx.sh index 99020d8..aea9a87 100755 --- a/mifosx.sh +++ b/mifosx.sh @@ -25,7 +25,6 @@ envsubst <${SOURCE_DIR}/schemapwd.sql | docker exec -i mariadb mysql mifosplatfo mkdir -p /srv/mifosx/conf envsubst <${SOURCE_DIR}/srv/mifosx/conf/context.xml >/srv/mifosx/conf/context.xml cp ${SOURCE_DIR}/srv/mifosx/conf/server.xml /srv/mifosx/conf/server.xml -cp ${SOURCE_DIR}/srv/mifosx/update-url.sh /srv/mifosx/update-url.sh # Populate database service mifosx start diff --git a/mifosx/adminpwd.sql b/mifosx/adminpwd.sql index 7d5dc85..0de25ed 100644 --- a/mifosx/adminpwd.sql +++ b/mifosx/adminpwd.sql @@ -1,6 +1,6 @@ UPDATE `m_appuser` SET `username` = "${MIFOSX_ADMIN_USER}", `password` = "${MIFOSX_ADMIN_HASH}", `email` = "${MIFOSX_ADMIN_EMAIL}" WHERE `id` = 1; -UPDATE `c_external_service_properties` SET `value` = "mifosx@spotter.ngo" WHERE `external_service_id` = 2 and `name` LIKE "username"; +UPDATE `c_external_service_properties` SET `value` = "admin@example.com" WHERE `external_service_id` = 2 and `name` LIKE "username"; UPDATE `c_external_service_properties` SET `value` = "" WHERE `external_service_id` = 2 and `name` LIKE "password"; UPDATE `c_external_service_properties` SET `value` = "postfix" WHERE `external_service_id` = 2 and `name` LIKE "host"; UPDATE `c_external_service_properties` SET `value` = "false" WHERE `external_service_id` = 2 and `name` LIKE "useTLS"; diff --git a/motech.sh b/motech.sh index 1603c42..7b0932a 100755 --- a/motech.sh +++ b/motech.sh @@ -25,7 +25,6 @@ cp ${SOURCE_DIR}/srv/motech/conf/config-locations.properties /srv/motech/conf/co cp ${SOURCE_DIR}/srv/motech/conf/config/motech-settings.properties /srv/motech/conf/config/motech-settings.properties cp ${SOURCE_DIR}/srv/motech/conf/config/org.motechproject.motech-platform-email/motech-email.properties /srv/motech/conf/config/org.motechproject.motech-platform-email/motech-email.properties chown -R 8013:8013 /srv/motech/conf -cp ${SOURCE_DIR}/srv/motech/update-url.sh /srv/motech/update-url.sh # Populate database and create admin account service motech start diff --git a/motech/srv/motech/update-url.sh b/motech/srv/motech/update-url.sh deleted file mode 100755 index f71af36..0000000 --- a/motech/srv/motech/update-url.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -HOST="${1}" -[ "${2}" != "443" ] && HOST="${1}:${2}" - -sed -i "s|^server\.url.*|server.url=https://${HOST}|" /srv/motech/conf/config/motech-settings.properties diff --git a/opendatakit.sh b/opendatakit.sh index de20f7a..060f757 100755 --- a/opendatakit.sh +++ b/opendatakit.sh @@ -24,7 +24,6 @@ mkdir -p /srv/opendatakit/conf envsubst <${SOURCE_DIR}/srv/opendatakit/conf/jdbc.properties >/srv/opendatakit/conf/jdbc.properties envsubst <${SOURCE_DIR}/srv/opendatakit/conf/security.properties >/srv/opendatakit/conf/security.properties cp ${SOURCE_DIR}/srv/opendatakit/conf/server.xml /srv/opendatakit/conf/server.xml -cp ${SOURCE_DIR}/srv/opendatakit/update-url.sh /srv/opendatakit/update-url.sh chown -R 8015:8015 /srv/opendatakit/conf # Populate database diff --git a/pandora.sh b/pandora.sh index 845df6f..ddce635 100755 --- a/pandora.sh +++ b/pandora.sh @@ -37,7 +37,6 @@ else fi cp ${SOURCE_DIR}/srv/pandora/conf/gunicorn_config.py /srv/pandora/conf/gunicorn_config.py envsubst <${SOURCE_DIR}/srv/pandora/conf/local_settings.py >/srv/pandora/conf/local_settings.py -cp ${SOURCE_DIR}/srv/pandora/update-url.sh /srv/pandora/update-url.sh # Set "production values" (increases performance) only if the DEBUG environment variable is not set if [ ${DEBUG:-0} -eq 0 ]; then diff --git a/pandora/srv/pandora/conf/local_settings.py b/pandora/srv/pandora/conf/local_settings.py index 0414cab..0e3648e 100644 --- a/pandora/srv/pandora/conf/local_settings.py +++ b/pandora/srv/pandora/conf/local_settings.py @@ -15,7 +15,7 @@ EMAIL_HOST = 'postfix' XACCELREDIRECT = True -GOOGLE_API_KEY = 'AIzaSyBvIF3D550tlpL6o1xRrDurGo-81VhHlOw' +GOOGLE_API_KEY = '' DEBUG = True JSON_DEBUG = DEBUG diff --git a/pandora/srv/pandora/update-url.sh b/pandora/srv/pandora/update-url.sh deleted file mode 100755 index 061597f..0000000 --- a/pandora/srv/pandora/update-url.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -HOST="${1}" -[ "${2}" != "443" ] && HOST="${1}:${2}" - -sed -i "s|^ \"url\":.*| \"url\": \"pandora.${HOST}\"|" /srv/pandora/conf/config.jsonc diff --git a/sahana-demo.sh b/sahana-demo.sh index 43df4f9..32d04c0 100755 --- a/sahana-demo.sh +++ b/sahana-demo.sh @@ -30,7 +30,6 @@ export SAHANADEMO_ADMIN_USER=admin@example.com export SAHANADEMO_ADMIN_PWD=$(head -c 12 /dev/urandom | base64) envsubst <${SOURCE_DIR}/srv/sahana-demo/conf/000_config.py >/srv/sahana-demo/conf/000_config.py envsubst <${SOURCE_DIR}/masterUsers.csv >/tmp/masterUsers.csv -cp ${SOURCE_DIR}/srv/sahana-demo/update-url.sh /srv/sahana-demo/update-url.sh spotter-appmgr update-login sahana-demo "${SAHANADEMO_ADMIN_USER}" "${SAHANADEMO_ADMIN_PWD}" # Populate database diff --git a/sahana-demo/srv/sahana-demo/conf/000_config.py b/sahana-demo/srv/sahana-demo/conf/000_config.py index 57fc322..18f2cb8 100644 --- a/sahana-demo/srv/sahana-demo/conf/000_config.py +++ b/sahana-demo/srv/sahana-demo/conf/000_config.py @@ -100,7 +100,7 @@ settings.mail.server = "postfix:25" #settings.mail.tls = True #settings.mail.login = "username:password" # From Address - until this is set, no mails can be sent -settings.mail.sender = "'Sahana' " +settings.mail.sender = "admin@example.com" # Default email address to which requests to approve new user accounts gets sent # This can be overridden for specific domains/organisations via the auth_domain table #settings.mail.approver = "useradmin@example.org" @@ -134,7 +134,7 @@ settings.frontpage.rss = [ # http://www.microsoft.com/maps/create-a-bing-maps-key.aspx #settings.gis.api_bing = "" # Google API Key (for Google Maps Layers) -settings.gis.api_google = "AIzaSyBvIF3D550tlpL6o1xRrDurGo-81VhHlOw" +settings.gis.api_google = "" # Yahoo API Key (for Geocoder) #settings.gis.api_yahoo = "" diff --git a/sahana-demo/srv/sahana-demo/update-url.sh b/sahana-demo/srv/sahana-demo/update-url.sh deleted file mode 100755 index 9a1fc4f..0000000 --- a/sahana-demo/srv/sahana-demo/update-url.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -HOST="${1}" -[ "${2}" != "443" ] && HOST="${1}:${2}" - -sed -i "s|^settings\.base\.public_url.*|settings.base.public_url = \"https://${HOST}\"|" /srv/sahana-demo/conf/000_config.py diff --git a/sahana.sh b/sahana.sh index fbb202f..e579648 100755 --- a/sahana.sh +++ b/sahana.sh @@ -33,7 +33,6 @@ export SAHANA_ADMIN_PWD=$(head -c 12 /dev/urandom | base64) envsubst <${SOURCE_DIR}/srv/sahana/conf/000_config.py >/srv/sahana/conf/000_config.py envsubst <${SOURCE_DIR}/srv/sahana/data/Spotter/masterUsers.csv >/srv/sahana/data/Spotter/masterUsers.csv cp ${SOURCE_DIR}/srv/sahana/conf/00_settings.py /srv/sahana/conf/00_settings.py -cp ${SOURCE_DIR}/srv/sahana/update-url.sh /srv/sahana/update-url.sh spotter-appmgr update-login sahana "${SAHANA_ADMIN_USER}" "${SAHANA_ADMIN_PWD}" # Populate database diff --git a/sahana/srv/sahana/conf/000_config.py b/sahana/srv/sahana/conf/000_config.py index aadae4c..b086ecb 100644 --- a/sahana/srv/sahana/conf/000_config.py +++ b/sahana/srv/sahana/conf/000_config.py @@ -100,10 +100,10 @@ settings.mail.server = "postfix:25" #settings.mail.tls = True #settings.mail.login = "username:password" # From Address - until this is set, no mails can be sent -settings.mail.sender = "'Sahana' " +settings.mail.sender = "admin@example.com" # Default email address to which requests to approve new user accounts gets sent # This can be overridden for specific domains/organisations via the auth_domain table -settings.mail.approver = "info@spotter.ngo" +settings.mail.approver = "admin@example.com" # Daily Limit on Sending of emails #settings.mail.limit = 1000 @@ -134,7 +134,7 @@ settings.frontpage.rss = [ # http://www.microsoft.com/maps/create-a-bing-maps-key.aspx #settings.gis.api_bing = "" # Google API Key (for Google Maps Layers) -settings.gis.api_google = "AIzaSyBvIF3D550tlpL6o1xRrDurGo-81VhHlOw" +settings.gis.api_google = "" # Yahoo API Key (for Geocoder) #settings.gis.api_yahoo = "" diff --git a/sahana/srv/sahana/update-url.sh b/sahana/srv/sahana/update-url.sh deleted file mode 100755 index 8b88341..0000000 --- a/sahana/srv/sahana/update-url.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -HOST="${1}" -[ "${2}" != "443" ] && HOST="${1}:${2}" - -sed -i "s|^settings\.base\.public_url.*|settings.base.public_url = \"https://${HOST}\"|" /srv/sahana/conf/000_config.py diff --git a/sambro.sh b/sambro.sh index 4518ddd..a95adc8 100755 --- a/sambro.sh +++ b/sambro.sh @@ -31,7 +31,6 @@ envsubst <${SOURCE_DIR}/srv/sambro/conf/000_config.py >/srv/sambro/conf/000_conf envsubst <${SOURCE_DIR}/masterUsers.csv >/tmp/masterUsers.csv cp ${SOURCE_DIR}/srv/sambro/conf/00_settings.py /srv/sambro/conf/00_settings.py cp ${SOURCE_DIR}/srv/sambro/data/SAMBRO/config.py /srv/sambro/data/SAMBRO/config.py -cp ${SOURCE_DIR}/srv/sambro/update-url.sh /srv/sambro/update-url.sh spotter-appmgr update-login sambro "${SAMBRO_ADMIN_USER}" "${SAMBRO_ADMIN_PWD}" # Populate database diff --git a/sambro/srv/sambro/conf/000_config.py b/sambro/srv/sambro/conf/000_config.py index 0efe87c..19cc2d8 100644 --- a/sambro/srv/sambro/conf/000_config.py +++ b/sambro/srv/sambro/conf/000_config.py @@ -100,10 +100,10 @@ settings.mail.server = "postfix:25" #settings.mail.tls = True #settings.mail.login = "username:password" # From Address - until this is set, no mails can be sent -settings.mail.sender = "'SAMBRO' " +settings.mail.sender = "admin@example.com" # Default email address to which requests to approve new user accounts gets sent # This can be overridden for specific domains/organisations via the auth_domain table -settings.mail.approver = "info@spotter.ngo" +settings.mail.approver = "admin@example.com" # Daily Limit on Sending of emails #settings.mail.limit = 1000 @@ -134,7 +134,7 @@ settings.frontpage.rss = [ # http://www.microsoft.com/maps/create-a-bing-maps-key.aspx #settings.gis.api_bing = "" # Google API Key (for Google Maps Layers) -settings.gis.api_google = "AIzaSyBvIF3D550tlpL6o1xRrDurGo-81VhHlOw" +settings.gis.api_google = "" # Yahoo API Key (for Geocoder) #settings.gis.api_yahoo = "" diff --git a/sambro/srv/sambro/update-url.sh b/sambro/srv/sambro/update-url.sh deleted file mode 100755 index 5e6e74b..0000000 --- a/sambro/srv/sambro/update-url.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -HOST="${1}" -[ "${2}" != "443" ] && HOST="${1}:${2}" - -sed -i "s|^settings\.base\.public_url.*|settings.base.public_url = \"https://${HOST}\"|" /srv/sambro/conf/000_config.py diff --git a/seeddms/srv/seeddms/conf/settings.xml b/seeddms/srv/seeddms/conf/settings.xml index 34b8e81..70b31ca 100644 --- a/seeddms/srv/seeddms/conf/settings.xml +++ b/seeddms/srv/seeddms/conf/settings.xml @@ -14,7 +14,7 @@ - + diff --git a/sigmah/srv/sigmah/conf/sigmah.properties b/sigmah/srv/sigmah/conf/sigmah.properties index c2cc462..5b84af7 100644 --- a/sigmah/srv/sigmah/conf/sigmah.properties +++ b/sigmah/srv/sigmah/conf/sigmah.properties @@ -24,7 +24,7 @@ files.upload.maxSize=20971520 mail.hostname=postfix mail.port=25 -mail.from.address=sigmah@spotter.ngo +mail.from.address=admin@example.com mail.from.name=Sigmah # Authentication (leave empty if no authentication is required). mail.auth.username= @@ -32,9 +32,9 @@ mail.auth.password= mail.encoding=UTF-8 mail.contentType=text/html -mail.support.to=sigmah@spotter.ngo +mail.support.to=admin@example.com # -- # MAPS API # -- -maps.key=AIzaSyBvIF3D550tlpL6o1xRrDurGo-81VhHlOw +maps.key= diff --git a/ushahidi.sh b/ushahidi.sh index 293561d..5217b07 100755 --- a/ushahidi.sh +++ b/ushahidi.sh @@ -22,7 +22,6 @@ mkdir -p /srv/ushahidi/conf /srv/ushahidi/data chown 8014:8014 /srv/ushahidi/data envsubst <${SOURCE_DIR}/srv/ushahidi/conf/env >/srv/ushahidi/conf/env cp ${SOURCE_DIR}/srv/ushahidi/conf/config.json /srv/ushahidi/conf/config.json -cp ${SOURCE_DIR}/srv/ushahidi/update-url.sh /srv/ushahidi/update-url.sh # Populate database docker run --rm -h ushahidi --link mariadb -v /srv/ushahidi/conf/env:/srv/ushahidi/platform/.env ushahidi /srv/ushahidi/platform/bin/phinx migrate -c /srv/ushahidi/platform/application/phinx.php diff --git a/ushahidi/srv/ushahidi/conf/config.json b/ushahidi/srv/ushahidi/conf/config.json index 7a46b18..64142f8 100644 --- a/ushahidi/srv/ushahidi/conf/config.json +++ b/ushahidi/srv/ushahidi/conf/config.json @@ -2,5 +2,5 @@ "backend_url": "https://ush.spotter.vm/platform", "client_id": "ushahidiui", "client_secret": "35e7f0bca957836d05ca0492211b0ac707671261", - "google_analytics_id": "AIzaSyBvIF3D550tlpL6o1xRrDurGo-81VhHlOw" + "google_analytics_id": "" } diff --git a/ushahidi/srv/ushahidi/update-url.sh b/ushahidi/srv/ushahidi/update-url.sh deleted file mode 100755 index 261b8ff..0000000 --- a/ushahidi/srv/ushahidi/update-url.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -HOST="${1}" -[ "${2}" != "443" ] && HOST="${1}:${2}" - -sed -i "s|^ \"backend_url\".*| \"backend_url\": \"https://${HOST}/platform\",|" /srv/ushahidi/conf/config.json - -if [ ! -e /run/openrc/started/mariadb ]; then - service mariadb start - STOP_MARIADB=1 -fi -API_URL='\\\"https:\\\\/\\\\/'${HOST}'\\\\/platform\\\\/api\\\\/v3\\\\/config\\\\/data-provider\\\"' -echo 'UPDATE `config` SET `config_value` = "'${API_URL}'" WHERE `group_name` LIKE "data-provider" AND `config_key` LIKE "url";' | docker exec -i mariadb mysql ushahidi -if [ ${STOP_MARIADB} ]; then - service mariadb stop -fi