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í.
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'] %}
+
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í.
+
+
Login:{{ app['login'] }}
+
Heslo:{{ app['password'] }}
+
+
+{% endif %}
+
+{% set app = conf['apps']['sahana-demo'] %}
+{% if app['visible'] %}
+
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.
+
+
+
+
+
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.
+
+
+
+
Aplikace
+
Zobrazena
+
Autostart
+
Stav
+
+
+
+ {% for app in conf['apps']|sort %}
+
+
{{ conf['apps'][app]['title'] }}
+
+
+
{% if is_service_started(app) %}Spuštěna (zastavit){% else %}Zastavena (spustit){% endif %}
+
+ {% endfor %}
+
+
+
+{% 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.
+
+
+
+
+
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 %}.
+
+
{{ conf['host']['domain'] }}
+
*.{{ conf['host']['domain'] }}
+
+
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
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'] }}.
+
+
+{% 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