Merge spotter-appmgr WSGI application
18
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
|
||||
|
6
basic/etc/init.d/spotter-appmgr
Executable file
@ -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"
|
@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ -x /usr/bin/acme.sh ] && /usr/bin/acme.sh --cron >/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"
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -1,308 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="author" content="TS">
|
||||
<meta name="copyright" content="page is under CC BY-NC-ND 3.0 CZ">
|
||||
<meta name="generator" content="Spotter.ngo">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Cluster NGO</title>
|
||||
<link rel="icon" href="img/cluster_spotter.png" type="image/png">
|
||||
<link rel="stylesheet" href="css/style.css" type="text/css" media="screen">
|
||||
<script src="js/jquery-3.3.1.min.js" type="text/javascript"></script>
|
||||
<script src="js/script.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>CLUSTER NGO</h1>
|
||||
<p>Sada softwarových nástrojů určená pro krizový management.</p>
|
||||
</header>
|
||||
|
||||
<div class="c c2" id="sahana">
|
||||
<h2><a href="https://sahana.{host}/eden/"><img src="img/EDEN.png" alt="Sahana EDEN" title="Sahana EDEN">Sahana EDEN</a></h2>
|
||||
<p><strong>Registr kontaktů</strong> asociací, organizací, jednotek zaměstnanců, dobrovolníků, <strong>Registr prostředků</strong>, materiálních zdrojů určených pro činnost v krizových situacích, <strong>logistika</strong> krizového zboží ve skladištích, úkrytech, <strong>organizace lidských zdrojů</strong>, diobrovolníků, <strong>mapová vizualizace</strong> pro lokalizaci a popis krizové události a <strong>mnoho dalších funkcí</strong>.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="sahana-demo">
|
||||
<h2><a href="https://sahana-demo.{host}/eden/"><img src="img/EDEN.png" alt="Sahana EDEN DEMO" title="Sahana EDEN DEMO">Sahana EDEN DEMO</a></h2>
|
||||
<p>Přístup určený k bezpečnému vyzkoušení aplikace. Zde můžete přidávat i mazat testovací data.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="sambro">
|
||||
<h2><a href="https://sambro.{host}/eden/"><img src="img/EDEN.png" alt="Sahana EDEN SAMBRO" title="Sahana EDEN SAMBRO">Sahana EDEN SAMBRO</a></h2>
|
||||
<p>Samostatná instance Sahana EDEN s šablonou SAMBRO.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="sambro-mobile">
|
||||
<h2><a href="#"><img src="img/EDEN.png" alt="SAMBRO Mobile" title="SAMBRO Mobile">SAMBRO Mobile</a></h2>
|
||||
<p>Mobilní klient k aplikaci Sahana EDEN.<br>
|
||||
<a href="https://itunes.apple.com/us/app/sambro-mobile/id1127251669"><img src="img/ios.png" class="ico" alt="IOS">IOS 6.0 a vyšší</a><br>
|
||||
<a href="https://apkpure.com/sambro-mobile/io.sahana.sambro.mobile"><img src="img/android.png" class="ico" alt="Android">Android 4.0 a vyšší</a>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">https://sambro.{host}/eden/</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="crisiscleanup">
|
||||
<h2><a href="https://cc.{host}"><img src="img/Crisis_Cleanup.png" alt="Crisis Cleanup" title="Crisis Cleanup">Crisis Cleanup</a></h2>
|
||||
<p><strong>Mapování krizové pomoci</strong> při odstraňování následků katastrof a koordinaci práce. Jde o majetek, ne o lidi.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="ckan">
|
||||
<h2><a href="https://ckan.{host}"><img src="img/CKAN.png" alt="CKAN" title="CKAN">CKAN</a></h2>
|
||||
<p><strong>Repository</strong> management a datová analýza pro vytváření otevřených dat.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="opendatakit-build">
|
||||
<h2><a href="https://odkbuild.{host}"><img src="img/ODK.png" alt="Open Data Kit" title="Open Data Kit">ODK Build</a></h2>
|
||||
<p><strong>Sběr dat s pomocí smartphone</strong>.<br>Aplikace pro návrh formulářů<br>
|
||||
<p><a href="http://opendatakit.org/xiframe/">XLSForm</a> - online konverter XLS.<br>
|
||||
<a href="https://opendatakit.org/downloads/download-info/odk-formuploader/"><img src="img/java.png" class="ico" alt="ODK Form Uploader">ODK Form Uploader</a><br>
|
||||
<a href="https://opendatakit.org/downloads/download-info/odk-validate-2/"><img src="img/java.png" class="ico" alt="ODK Validate">ODK Validate</a></p>
|
||||
</div>
|
||||
|
||||
<div class="c" id="opendatakit-clients">
|
||||
<h2><a href="#"><img src="img/ODK_Collect.png" alt="Open Data Kit" title="Open Data Kit">ODK Collect</a></h2>
|
||||
<p>Mobilní aplikace<br>
|
||||
<a href="https://play.google.com/store/apps/details?id=org.odk.collect.android"><img src="img/android.png" class="ico" alt="ODK Collect">ODK Collect pro Android</a><br>
|
||||
<a href="https://opendatakit.org/downloads/download-info/odk-briefcase/"><img src="img/java.png" class="ico" alt="ODK Briefcase">ODK Briefcase</a><br>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">https://odk.{host}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="opendatakit">
|
||||
<h2><a href="https://odk.{host}/"><img src="img/ODK.png" alt="Open Data Kit" title="Open Data Kit">ODK Aggregate</a></h2>
|
||||
<p><strong>Sběr dat s pomocí smartphone</strong>.<br>
|
||||
<a href="http://geoodk.com">GeoODK Collect</a> - náhrada papírových dotazníků smartphonem.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="openmapkit">
|
||||
<h2><a href="https://omk.{host}"><img src="img/OMK.png" alt="Open Map Kit" title="Open Map Kit">OpenMapKit Server</a></h2>
|
||||
<p><strong>Sběr dat s pomocí smartphone</strong>.<br>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="geoodk-clients">
|
||||
<h2><a href="#"><img src="img/GeoODK_Collect.png" alt="GeoODK Collect" title="GeoODK Collect">GeoODK Collect</a></h2>
|
||||
<p>Mobilní aplikace<br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.geoodk.collect.android"><img src="img/android.png" class="ico" alt="GeoODK Collect">GeoODK Collect pro Android</a>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">https://omk.{host}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="openmapkit-clients">
|
||||
<h2><a href="#"><img src="img/OMK.png" alt="Open Map Kit" title="Open Map Kit">OpenMapKit</a></h2>
|
||||
<p>Mobilní aplikace<br>
|
||||
<a href="https://play.google.com/store/apps/details?id=org.odk.collect.android"><img src="img/android.png" class="ico" alt="ODK Collect">ODK Collect pro Android</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=org.redcross.openmapkit"><img src="img/android.png" class="ico" alt="Android">OpenMapKit pro Android 4.1 a vyšší</a>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">https://omk.{host}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="frontlinesms">
|
||||
<h2><a href="https://sms.{host}"><img src="img/FrontlineSMS.png" alt="FrontlineSMS" title="FrontlineSMS">FrontlineSMS</a></h2>
|
||||
<p><strong>SMS messaging</strong> přes veřejné datové brány</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="seeddms">
|
||||
<h2><a href="https://dms.{host}"><img src="img/SeedDMS.png" alt="SeedDMS" title="SeedDMS">SeedDMS</a></h2>
|
||||
<p><strong>Dokument management</strong> na dokumentaci a projektovou dokumentaci</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="pandora">
|
||||
<h2><a href="https://pandora.{host}"><img src="img/Pandora.png" alt="Pan.do/ra" title="Pan.do/ra">Pan.do/ra</a></h2>
|
||||
<p><strong>Media management</strong> na foto a video z krizové události. Tvorba metadat, komentářů, lokalizace v čase a na mapě.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="ushahidi">
|
||||
<h2><a href="https://ush.{host}"><img src="img/Ushahidi.png" alt="Ushahidi" title="Ushahidi">Ushahidi</a></h2>
|
||||
<p>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.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="ushahidi-mobile">
|
||||
<h2><a href="#"><img src="img/Ushahidi_mobile.png" alt="Ushahidi" title="Ushahidi">Ushahidi Mobile</a></h2>
|
||||
<p>Mobilní aplikace Ushahidi pro<br>
|
||||
<a href="https://itunes.apple.com/us/app/ushahidi-mobile/id1205994516?mt=8"><img src="img/ios.png" class="ico" alt="IOS">IOS 9.0 a vyšší</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.ushahidi.mobile"><img src="img/android.png" class="ico" alt="Android">Android 4.4 a vyšší</a>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">ush.{host}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="sms-sync-gateway">
|
||||
<h2><a href="#"><img src="img/SMS_Sync.png" alt="SMS Sync Gateway" title="SMS Sync Gateway">SMS Sync Gateway</a></h2>
|
||||
<p>Mobilní aplikace pro<br>
|
||||
<a href="https://play.google.com/store/apps/details?id=org.addhen.smssync"><img src="img/android.png" class="ico" alt="Android">Android 2.3 a vyšší</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="c" id="kanboard">
|
||||
<h2><a href="https://kb.{host}"><img src="img/Kanboard.png" alt="Kanboard" title="Kanboard">Kanboard</a></h2>
|
||||
<p>Usnadňuje tvorbu a řízení projektů s pomocí Kanban metodiky.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="kanboard-mobile">
|
||||
<h2><a href="#"><img src="img/Kanboard.png" alt="Kanboard" title="Kanboard">Kanboard Mobile</a></h2>
|
||||
<p>Mobilní aplikace<br>
|
||||
<a href="https://play.google.com/store/apps/details?id=eu.it_quality.kanboard"><img src="img/android.png" class="ico" alt="KanBoard">KanBoard client pro Android 4.1 a vyšší</a><br>
|
||||
<a href="https://f-droid.org/packages/in.andres.kandroid/"><img src="img/android.png" class="ico" alt="Android">Kandroid pro Android 4.2 a vyšší</a>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">https://kb.{host}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="cts">
|
||||
<h2><a href="https://cts.{host}"><img src="img/CTS.png" alt="CTS" title="CTS">CTS</a></h2>
|
||||
<p>Logistika hmotné pomoci pro humanitární potřeby.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="gnuhealth">
|
||||
<h2><a href="https://gh.{host}/index.html"><img src="img/GNU_Health.png" alt="GNU Health" title="GNU Health">GNU Health</a></h2>
|
||||
<p>Zdravotní a nemocniční informační systém.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
<li><strong>Heslo k demu:</strong> <span class="demopassword">gnusolidario</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="gnuhealth-clients">
|
||||
<h2><a href="#"><img src="img/GNU_Health.png" alt="GNU Health" title="GNU Health">GNU Health klienti</a></h2>
|
||||
<p>Klientské aplikace platformy Tryton GNU Health pro<br>
|
||||
<a href="https://downloads.tryton.org/4.2/tryton-last.exe"><img src="img/Windows.png" class="ico" alt="Windows">Windows</a><br>
|
||||
<a href="https://downloads.tryton.org/4.2/tryton-last.dmg"><img src="img/MacOS.png" class="ico" alt="MacOS">MacOS</a><br>
|
||||
<a href="https://downloads.tryton.org/4.2/tryton-last.tar.gz"><img src="img/Linux.png" class="ico" alt="Linux">Linux</a>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">gh.{host}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="sigmah">
|
||||
<h2><a href="https://sigmah.{host}/sigmah/"><img src="img/Sigmah.png" alt="Sigmah" title="Sigmah">Sigmah</a></h2>
|
||||
<p>Rozpočtování získávání finančních prostředků.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="motech">
|
||||
<h2><a href="https://motech.{host}/"><img src="img/Motech.png" alt="Motech" title="Motech">Motech</a></h2>
|
||||
<p>Integrace zdravotnických a komunikačních služeb.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="mifosx">
|
||||
<h2><a href="https://mifosx.{host}/"><img src="img/MifosX.png" alt="Mifos X" title="Mifos X">Mifos X</a></h2>
|
||||
<p>Nástroj na rozvojovou, humanitární pomoc a mikrofinancování.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="mifosx-mobile">
|
||||
<h2><a href="#"><img src="img/MifosX_Mobile.png" alt="Mifos X" title="Mifos X">Mifos X</a></h2>
|
||||
<p>Mobilní aplikace<br>
|
||||
<a href="https://github.com/openMF/android-client/releases"><img src="img/android.png" class="ico" alt="Mifos X">Mifos X client pro Android 3.0 a vyšší</a><br>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">mifosx.{host}</span></li>
|
||||
<li><strong>Tenant ID:</strong> <span>default</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="diaspora">
|
||||
<h2><a href="#"><img src="img/Diaspora.png" alt="diaspora*" title="diaspora*">diaspora*</a></h2>
|
||||
<p>Autonomní sociání síť s možností propojení do cizích sociálních sítí.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login"></span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="c" id="openid">
|
||||
<h2><a href="http://openid.net"><img src="img/OpenID.png" alt="OpenID" title="OpenID">OpenID</a></h2>
|
||||
<p>Pro ověření identity budete potřebovat účet OpenID. Zaregistrujte se. Registraci využijete v software Sahana EDEN.</p>
|
||||
</div>
|
||||
|
||||
<div class="c" id="posm">
|
||||
<h2><a href="#"><img src="img/POSM.png" alt="POSM" title="POSM">POSM</a></h2>
|
||||
<p><strong>Portable Open Street Map</strong> - softwarový balík na offline používání OpenStreet Map v samostatné virtuální image.</p>
|
||||
</div>
|
||||
|
||||
<div class="c visible" id="cluster-spotter">
|
||||
<h2><a href="http://spotter.ngo"><img src="img/cluster_spotter.png" alt="Cluster Spotter" title="Cluster Spotter">Cluster Spotter</a></h2>
|
||||
<p>Info o Misi a Vizi projektu, včetně kontaktu. Zachovejte data bezpečná a neposkytujte je nepovolaným osobám.<br>
|
||||
<small>CC 4.0 CZ by <a href="http://trendspotter.cz">TS</a>. Content is based on PD, CC, GNU/GPL. Brand names, trademarks belong to their respective holders.</small>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="cleaner"></div>
|
||||
</body>
|
||||
</html>
|
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
336
basic/srv/spotter/appmgr/__init__.py
Normal file
@ -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()
|
105
basic/srv/spotter/appmgr/confupdater.py
Normal file
@ -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', ' <smtp smtpServer="postfix" smtpPort="25" smtpSendFrom=', '"{}" smtpUser="" smtpPassword=""/>'.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')
|
99
basic/srv/spotter/appmgr/tools.py
Normal file
@ -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
|
28
basic/srv/spotter/appmgr/validator.py
Normal file
@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
|
||||
domain_re = re.compile(r'^(?!-)[a-z0-9-]{1,63}(?<!-)(?:\.(?!-)[a-z0-9-]{1,63}(?<!-)){0,125}\.(?!-)(?![0-9]+$)[a-z0-9-]{1,63}(?<!-)$')
|
||||
box_re = re.compile(r'^[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*$')
|
||||
|
||||
class InvalidValueException(Exception):
|
||||
pass
|
||||
|
||||
def is_valid_domain(domain):
|
||||
return bool(domain_re.match(domain))
|
||||
|
||||
def is_valid_port(port):
|
||||
try:
|
||||
port = int(port)
|
||||
return port > 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]))
|
234
basic/srv/spotter/appmgr/wsgiapp.py
Normal file
@ -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 href="{}">{}</a> 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': '<span class="info">Spuštěna</span> (<a href="#" class="app-stop">zastavit</a>)',
|
||||
'app_stopped': '<span class="error">Zastavena</span> (<a href="#" class="app-start">spustit</a>)',
|
||||
'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
|
96
basic/srv/spotter/cli.py
Executable file
@ -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)
|
138
basic/srv/spotter/config.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
172
basic/srv/spotter/static/css/style.css
Normal file
@ -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); }
|
||||
}
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
163
basic/srv/spotter/static/js/script.js
Normal file
@ -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('<div class="loader"></div>');
|
||||
$.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('<div class="loader"></div>');
|
||||
$.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;
|
||||
}
|
28
basic/srv/spotter/templates/layout.html
Normal file
@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="author" content="TS">
|
||||
<meta name="copyright" content="page is under CC BY-NC-ND 3.0 CZ">
|
||||
<meta name="generator" content="Spotter.ngo">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<link rel="icon" href="static/img/Cluster_Spotter.png" type="image/png">
|
||||
<link rel="stylesheet" href="static/css/style.css" type="text/css" media="screen">
|
||||
<script src="static/js/jquery-3.3.1.min.js"></script>
|
||||
<script src="static/js/script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
{% set template = self._TemplateReference__context.name %}
|
||||
{% if template != 'portal.html' %}<a href="/">Portál</a>{% endif %}
|
||||
{% if template != 'setup-host.html' %}<a href="/setup-host">Nastavení hostitele</a>{% endif %}
|
||||
{% if template != 'setup-apps.html' %}<a href="/setup-apps">Nastavení aplikací</a>{% endif %}
|
||||
</nav>
|
||||
<header>
|
||||
<h1>CLUSTER NGO</h1>
|
||||
<p>Sada softwarových nástrojů určená pro krizový management.</p>
|
||||
</header>
|
||||
{% block body %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
345
basic/srv/spotter/templates/portal.html
Normal file
@ -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'] %}
|
||||
<div class="portal-box portal-box-double-width">
|
||||
<h2><a href="https://sahana.{{ host }}/eden/"><img src="static/img/EDEN.png" alt="Sahana EDEN" title="Sahana EDEN">Sahana EDEN</a></h2>
|
||||
<p><strong>Registr kontaktů</strong> asociací, organizací, jednotek zaměstnanců, dobrovolníků, <strong>Registr prostředků</strong>, materiálních zdrojů určených pro činnost v krizových situacích, <strong>logistika</strong> krizového zboží ve skladištích, úkrytech, <strong>organizace lidských zdrojů</strong>, diobrovolníků, <strong>mapová vizualizace</strong> pro lokalizaci a popis krizové události a <strong>mnoho dalších funkcí</strong>.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['sahana-demo'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://sahana-demo.{{ host }}/eden/"><img src="static/img/EDEN.png" alt="Sahana EDEN DEMO" title="Sahana EDEN DEMO">Sahana EDEN DEMO</a></h2>
|
||||
<p>Přístup určený k bezpečnému vyzkoušení aplikace. Zde můžete přidávat i mazat testovací data.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['sambro'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://sambro.{{ host }}/eden/"><img src="static/img/EDEN.png" alt="Sahana EDEN SAMBRO" title="Sahana EDEN SAMBRO">Sahana EDEN SAMBRO</a></h2>
|
||||
<p>Samostatná instance Sahana EDEN s šablonou SAMBRO.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="portal-box">
|
||||
<h2><a href="#"><img src="static/img/EDEN.png" alt="SAMBRO Mobile" title="SAMBRO Mobile">SAMBRO Mobile</a></h2>
|
||||
<p>Mobilní klient k aplikaci Sahana EDEN.<br>
|
||||
<a href="https://itunes.apple.com/us/app/sambro-mobile/id1127251669"><img src="static/img/icons/iOS.png" class="ico" alt="IOS">IOS 6.0 a vyšší</a><br>
|
||||
<a href="https://apkpure.com/sambro-mobile/io.sahana.sambro.mobile"><img src="static/img/icons/Android.png" class="ico" alt="Android">Android 4.0 a vyšší</a>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">https://sambro.{{ host }}/eden/</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['crisiscleanup'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://cc.{{ host }}"><img src="static/img/Crisis_Cleanup.png" alt="Crisis Cleanup" title="Crisis Cleanup">Crisis Cleanup</a></h2>
|
||||
<p><strong>Mapování krizové pomoci</strong> při odstraňování následků katastrof a koordinaci práce. Jde o majetek, ne o lidi.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['ckan'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://ckan.{{ host }}"><img src="static/img/CKAN.png" alt="CKAN" title="CKAN">CKAN</a></h2>
|
||||
<p><strong>Repository</strong> management a datová analýza pro vytváření otevřených dat.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['opendatakit-build'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://odkbuild.{{ host }}"><img src="static/img/ODK.png" alt="Open Data Kit" title="Open Data Kit">ODK Build</a></h2>
|
||||
<p><strong>Sběr dat s pomocí smartphone</strong>.<br>Aplikace pro návrh formulářů<br>
|
||||
<p><a href="http://opendatakit.org/xiframe/">XLSForm</a> - online konverter XLS.<br>
|
||||
<a href="https://opendatakit.org/downloads/download-info/odk-formuploader/"><img src="static/img/icons/Java.png" class="ico" alt="ODK Form Uploader">ODK Form Uploader</a><br>
|
||||
<a href="https://opendatakit.org/downloads/download-info/odk-validate-2/"><img src="static/img/icons/Java.png" class="ico" alt="ODK Validate">ODK Validate</a></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['opendatakit'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="#"><img src="static/img/ODK_Collect.png" alt="Open Data Kit" title="Open Data Kit">ODK Collect</a></h2>
|
||||
<p>Mobilní aplikace<br>
|
||||
<a href="https://play.google.com/store/apps/details?id=org.odk.collect.android"><img src="static/img/icons/Android.png" class="ico" alt="ODK Collect">ODK Collect pro Android</a><br>
|
||||
<a href="https://opendatakit.org/downloads/download-info/odk-briefcase/"><img src="static/img/icons/Java.png" class="ico" alt="ODK Briefcase">ODK Briefcase</a><br>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">https://odk.{{ host }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://odk.{{ host }}/"><img src="static/img/ODK.png" alt="Open Data Kit" title="Open Data Kit">ODK Aggregate</a></h2>
|
||||
<p><strong>Sběr dat s pomocí smartphone</strong>.<br>
|
||||
<a href="http://geoodk.com">GeoODK Collect</a> - náhrada papírových dotazníků smartphonem.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['openmapkit'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://omk.{{ host }}"><img src="static/img/OMK.png" alt="Open Map Kit" title="Open Map Kit">OpenMapKit Server</a></h2>
|
||||
<p><strong>Sběr dat s pomocí smartphone</strong>.<br>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="portal-box">
|
||||
<h2><a href="#"><img src="static/img/GeoODK_Collect.png" alt="GeoODK Collect" title="GeoODK Collect">GeoODK Collect</a></h2>
|
||||
<p>Mobilní aplikace<br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.geoodk.collect.android"><img src="static/img/icons/Android.png" class="ico" alt="GeoODK Collect">GeoODK Collect pro Android</a>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">https://omk.{{ host }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="portal-box">
|
||||
<h2><a href="#"><img src="static/img/OMK.png" alt="Open Map Kit" title="Open Map Kit">OpenMapKit</a></h2>
|
||||
<p>Mobilní aplikace<br>
|
||||
<a href="https://play.google.com/store/apps/details?id=org.odk.collect.android"><img src="static/img/icons/Android.png" class="ico" alt="ODK Collect">ODK Collect pro Android</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=org.redcross.openmapkit"><img src="static/img/icons/Android.png" class="ico" alt="Android">OpenMapKit pro Android 4.1 a vyšší</a>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">https://omk.{{ host }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['frontlinesms'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://sms.{{ host }}"><img src="static/img/FrontlineSMS.png" alt="FrontlineSMS" title="FrontlineSMS">FrontlineSMS</a></h2>
|
||||
<p><strong>SMS messaging</strong> přes veřejné datové brány</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="portal-box">
|
||||
<h2><a href="#"><img src="static/img/SMS_Sync.png" alt="SMS Sync Gateway" title="SMS Sync Gateway">SMS Sync Gateway</a></h2>
|
||||
<p>Mobilní aplikace pro<br>
|
||||
<a href="https://play.google.com/store/apps/details?id=org.addhen.smssync"><img src="static/img/icons/Android.png" class="ico" alt="Android">Android 2.3 a vyšší</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['seeddms'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://dms.{{ host }}"><img src="static/img/SeedDMS.png" alt="SeedDMS" title="SeedDMS">SeedDMS</a></h2>
|
||||
<p><strong>Dokument management</strong> na dokumentaci a projektovou dokumentaci</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['pandora'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://pandora.{{ host }}"><img src="static/img/Pandora.png" alt="Pan.do/ra" title="Pan.do/ra">Pan.do/ra</a></h2>
|
||||
<p><strong>Media management</strong> na foto a video z krizové události. Tvorba metadat, komentářů, lokalizace v čase a na mapě.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['ushahidi'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://ush.{{ host }}"><img src="static/img/Ushahidi.png" alt="Ushahidi" title="Ushahidi">Ushahidi</a></h2>
|
||||
<p>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.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="portal-box">
|
||||
<h2><a href="#"><img src="static/img/Ushahidi_mobile.png" alt="Ushahidi" title="Ushahidi">Ushahidi Mobile</a></h2>
|
||||
<p>Mobilní aplikace Ushahidi pro<br>
|
||||
<a href="https://itunes.apple.com/us/app/ushahidi-mobile/id1205994516?mt=8"><img src="static/img/icons/iOS.png" class="ico" alt="IOS">IOS 9.0 a vyšší</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.ushahidi.mobile"><img src="static/img/icons/Android.png" class="ico" alt="Android">Android 4.4 a vyšší</a>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">ush.{{ host }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['kanboard'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://kb.{{ host }}"><img src="static/img/Kanboard.png" alt="Kanboard" title="Kanboard">Kanboard</a></h2>
|
||||
<p>Usnadňuje tvorbu a řízení projektů s pomocí Kanban metodiky.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="portal-box">
|
||||
<h2><a href="#"><img src="static/img/Kanboard.png" alt="Kanboard" title="Kanboard">Kanboard Mobile</a></h2>
|
||||
<p>Mobilní aplikace<br>
|
||||
<a href="https://play.google.com/store/apps/details?id=eu.it_quality.kanboard"><img src="static/img/icons/Android.png" class="ico" alt="KanBoard">KanBoard client pro Android 4.1 a vyšší</a><br>
|
||||
<a href="https://f-droid.org/packages/in.andres.kandroid/"><img src="static/img/icons/Android.png" class="ico" alt="Android">Kandroid pro Android 4.2 a vyšší</a>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">https://kb.{{ host }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['cts'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://cts.{{ host }}"><img src="static/img/CTS.png" alt="CTS" title="CTS">CTS</a></h2>
|
||||
<p>Logistika hmotné pomoci pro humanitární potřeby.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['gnuhealth'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://gh.{{ host }}/index.html"><img src="static/img/GNU_Health.png" alt="GNU Health" title="GNU Health">GNU Health</a></h2>
|
||||
<p>Zdravotní a nemocniční informační systém.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
<li><strong>Heslo k demu:</strong> <span class="demopassword">gnusolidario</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="portal-box">
|
||||
<h2><a href="#"><img src="static/img/GNU_Health.png" alt="GNU Health" title="GNU Health">GNU Health klienti</a></h2>
|
||||
<p>Klientské aplikace platformy Tryton GNU Health pro<br>
|
||||
<a href="https://downloads.tryton.org/4.2/tryton-last.exe"><img src="static/img/icons/Windows.png" class="ico" alt="Windows">Windows</a><br>
|
||||
<a href="https://downloads.tryton.org/4.2/tryton-last.dmg"><img src="static/img/icons/MacOS.png" class="ico" alt="MacOS">MacOS</a><br>
|
||||
<a href="https://downloads.tryton.org/4.2/tryton-last.tar.gz"><img src="static/img/icons/Linux.png" class="ico" alt="Linux">Linux</a>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">gh.{{ host }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['sigmah'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://sigmah.{{ host }}/sigmah/"><img src="static/img/Sigmah.png" alt="Sigmah" title="Sigmah">Sigmah</a></h2>
|
||||
<p>Rozpočtování získávání finančních prostředků.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['motech'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://motech.{{ host }}/"><img src="static/img/Motech.png" alt="Motech" title="Motech">Motech</a></h2>
|
||||
<p>Integrace zdravotnických a komunikačních služeb.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set app = conf['apps']['mifosx'] %}
|
||||
{% if app['visible'] %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://mifosx.{{ host }}/"><img src="static/img/MifosX.png" alt="Mifos X" title="Mifos X">Mifos X</a></h2>
|
||||
<p>Nástroj na rozvojovou, humanitární pomoc a mikrofinancování.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="portal-box">
|
||||
<h2><a href="#"><img src="static/img/MifosX_Mobile.png" alt="Mifos X" title="Mifos X">Mifos X</a></h2>
|
||||
<p>Mobilní aplikace<br>
|
||||
<a href="https://github.com/openMF/android-client/releases"><img src="static/img/icons/Android.png" class="ico" alt="Mifos X">Mifos X client pro Android 3.0 a vyšší</a><br>
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>URL:</strong> <span class="clienturl">mifosx.{{ host }}</span></li>
|
||||
<li><strong>Tenant ID:</strong> <span>default</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if false %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="#"><img src="static/img/Diaspora.png" alt="diaspora*" title="diaspora*">diaspora*</a></h2>
|
||||
<p>Autonomní sociání síť s možností propojení do cizích sociálních sítí.</p>
|
||||
<ul>
|
||||
<li><strong>Login:</strong> <span class="login">{{ app['login'] }}</span></li>
|
||||
<li><strong>Heslo:</strong> <span class="password">{{ app['password'] }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="portal-box">
|
||||
<h2><a href="http://openid.net"><img src="static/img/OpenID.png" alt="OpenID" title="OpenID">OpenID</a></h2>
|
||||
<p>Pro ověření identity budete potřebovat účet OpenID. Zaregistrujte se. Registraci využijete v software Sahana EDEN.</p>
|
||||
</div>
|
||||
|
||||
<div class="portal-box">
|
||||
<h2><a href="#"><img src="static/img/POSM.png" alt="POSM" title="POSM">POSM</a></h2>
|
||||
<p><strong>Portable Open Street Map</strong> - softwarový balík na offline používání OpenStreet Map v samostatné virtuální image.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="portal-box">
|
||||
<h2><a href="http://spotter.ngo"><img src="static/img/Cluster_Spotter.png" alt="Cluster Spotter" title="Cluster Spotter">Cluster Spotter</a></h2>
|
||||
<p>Info o Misi a Vizi projektu, včetně kontaktu. Zachovejte data bezpečná a neposkytujte je nepovolaným osobám.<br>
|
||||
<small>CC 4.0 CZ by <a href="http://trendspotter.cz">TS</a>. Content is based on PD, CC, GNU/GPL. Brand names, trademarks belong to their respective holders.</small>
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
58
basic/srv/spotter/templates/setup-apps.html
Normal file
@ -0,0 +1,58 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% block title %}Nastavení aplikací{% endblock %}
|
||||
{% block body %}
|
||||
<div class="setup-box">
|
||||
<h2>Nastavení aplikací</h2>
|
||||
<p>Společné nastavení sdílené některými aplikacemi.</p>
|
||||
<form id="update-common" action="/update-common" method="post">
|
||||
<table>
|
||||
<tr>
|
||||
<td>E-mail</td>
|
||||
<td><input type="text" name="email" id="email" value="{{ conf['common']['email'] }}"></td>
|
||||
<td class="remark">Administrativní 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.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Google Maps API klíč</td>
|
||||
<td><input type="text" name="gmaps-api-key" id="gmaps-api-key" value="{{ conf['common']['gmaps-api-key'] }}"></td>
|
||||
<td class="remark">API klíč pro službu Google Maps, která je využita některými aplikacemi.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td colspan="2">
|
||||
<input type="submit" id="common-submit" value="Nastavit hodnoty">
|
||||
<div id="common-message"></div>
|
||||
<div id="common-wait" class="loader-wrap">
|
||||
<div class="loader"></div>
|
||||
<span>Provádí se změna nastavení, prosím čekejte...</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="setup-box">
|
||||
<h2>Správce aplikací</h2>
|
||||
<p>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.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Aplikace</td>
|
||||
<td>Zobrazena</td>
|
||||
<td>Autostart</td>
|
||||
<td>Stav</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for app in conf['apps']|sort %}
|
||||
<tr data-app="{{ app }}">
|
||||
<td>{{ conf['apps'][app]['title'] }}</td>
|
||||
<td class="center"><input type="checkbox" class="app-visible"{% if conf['apps'][app]['visible'] %} checked{% endif %}></td>
|
||||
<td class="center"><input type="checkbox" class="app-autostart"{% if is_service_autostarted(app) %} checked{% endif %}></td>
|
||||
<td>{% if is_service_started(app) %}<span class="info">Spuštěna</span> (<a href="#" class="app-stop">zastavit</a>){% else %}<span class="error">Zastavena</span> (<a href="#" class="app-start">spustit</a>){% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
115
basic/srv/spotter/templates/setup-host.html
Normal file
@ -0,0 +1,115 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% block title %}Nastavení hostitele{% endblock %}
|
||||
{% block body %}
|
||||
<div class="setup-box">
|
||||
<h2>HTTPS Hostitel</h2>
|
||||
<p>Základní doménové jméno a HTTPS port na kterých budou přístupny všechny aplikace.</p>
|
||||
<form id="update-host" action="/update-host" method="post">
|
||||
<table>
|
||||
<tr>
|
||||
<td>Doména</td>
|
||||
<td><input type="text" name="domain" id="domain" value="{{ conf['host']['domain'] }}"></td>
|
||||
<td class="remark">Plně 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.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Port</td>
|
||||
<td><input type="text" name="port" id="port" value="{{ conf['host']['port'] }}"></td>
|
||||
<td class="remark">HTTPS port na kterém budou dostupné aplikace. Výchozí HTTPS port je 443.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td colspan="2">
|
||||
<input type="submit" id="host-submit" value="Nastavit hostitele">
|
||||
<div id="host-message"></div>
|
||||
<div id="host-wait" class="loader-wrap">
|
||||
<div class="loader"></div>
|
||||
<span>Provádí se změna nastavení, prosím čekejte...</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="setup-box">
|
||||
<h2>DNS záznamy</h2>
|
||||
<p>Na jmenném serveru domény nastavené v sekci <em>HTTPS Hostitel</em> 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.</p>
|
||||
<p>Vnější IPv4 {% if ex_ipv4 %}je <strong>{{ ex_ipv4 }}</strong>{% else %}nebyla zjištěna{% endif %} a IPv6 {% if ex_ipv6 %}je <strong>{{ ex_ipv6 }}</strong>{% else %}nebyla zjištěna{% endif %}.</p>
|
||||
<ul>
|
||||
<li>{{ conf['host']['domain'] }}</li>
|
||||
<li>*.{{ conf['host']['domain'] }}</li>
|
||||
</ul>
|
||||
<p>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</p>
|
||||
<ul style="column-count:3">
|
||||
<li>{{ conf['host']['domain'] }}</li>
|
||||
{% for app in conf['apps']|sort %}
|
||||
<li>{{ conf['apps'][app]['host'] }}.{{ conf['host']['domain'] }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<input type="button" id="verify-dns" value="Ověřit nastavení DNS">
|
||||
<div id="dns-message"></div>
|
||||
<div id="dns-wait" class="loader-wrap">
|
||||
<div class="loader"></div>
|
||||
<span>Ověřuje se nastavení DNS, prosím čekejte...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setup-box">
|
||||
<h2>Firewall a NAT</h2>
|
||||
<p>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 <em>HTTPS Hostitel</em>) z internetu korektně nasměrován na místní adresu virtuálního stroje.</p>
|
||||
<p>Pokud bude využit systém automatického vyžádání a obnovy certifikátu (sekce <em>HTTPS certifikát</em>), 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 <em>DNS záznamy</em>.</p>
|
||||
<p>Místní IPv4 {% if in_ipv4 %}je <strong>{{ in_ipv4 }}</strong>{% else %}nebyla zjištěna{% endif %} a IPv6 {% if in_ipv6 %}je <strong>{{ in_ipv6 }}</strong>{% else %}nebyla zjištěna{% endif %}.</p>
|
||||
<input type="button" id="verify-https" value="Ověřit nastavení portu {{ conf['host']['port'] }}">
|
||||
<div id="https-message"></div>
|
||||
<div id="https-wait" class="loader-wrap">
|
||||
<div class="loader"></div>
|
||||
<span>Ověřuje se nastavení firewallu a NAT pro port {{ conf['host']['port'] }}, prosím čekejte...</span>
|
||||
</div>
|
||||
<input type="button" id="verify-http" value="Ověřit nastavení portu 80">
|
||||
<div id="http-message"></div>
|
||||
<div id="http-wait" class="loader-wrap">
|
||||
<div class="loader"></div>
|
||||
<span>Ověřuje se nastavení firewallu a NAT pro port 80, prosím čekejte...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setup-box">
|
||||
<h2>HTTPS certifikát</h2>
|
||||
<p>Stávající certifikát je vystaven na jméno <strong>{{ cert_info['subject']['commonName'] }}</strong> vystavitelem <strong>{{ cert_info['issuer']['commonName'] }}</strong> a jeho platnost vyprší <strong>{{ cert_info['notAfter'] }}</strong>.</p>
|
||||
<form id="update-cert" action="/update-cert" method="post">
|
||||
<table>
|
||||
<tr>
|
||||
<td>Způsob správy</td>
|
||||
<td>
|
||||
<select name="method" id="cert-method">
|
||||
<option value="auto"{% if is_letsencrypt %} selected{% endif %}>Automaticky</option>
|
||||
<option value="manual"{% if not is_letsencrypt %} selected{% endif %}>Ručně</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="remark">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 <em>DNS záznamy</em> a nainstaluje úlohu pro jeho automatickou obnovu. Tato akce může trvat několik minut.<br>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.</td>
|
||||
</tr>
|
||||
<tr class="cert-upload"{% if is_letsencrypt %} style="display:none"{% endif %}>
|
||||
<td>Soubor certifikátu</td>
|
||||
<td><input type="file" name="public" accept=".cer, .crt, .der, .pem"></td>
|
||||
<td class="remark">Soubor s certifikátem ve formátu PEM.<br>Pokud je podepsán certifikační autoritou třetí strany, pak by tento soubor měl mimo koncového certifikátu obsahovat i podpisový certifikát.</td>
|
||||
</tr>
|
||||
<tr class="cert-upload"{% if is_letsencrypt %} style="display:none"{% endif %}>
|
||||
<td>Soubor klíče</td>
|
||||
<td><input type="file" name="private" accept=".key, .pem"></td>
|
||||
<td class="remark">Soubor se soukromým klíčem ve formátu PEM pro výše vybraný certifikát.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td colspan="2">
|
||||
<input type="submit" id="cert-submit" value="Nastavit certifikát">
|
||||
<div id="cert-message"></div>
|
||||
<div id="cert-wait" class="loader-wrap">
|
||||
<div class="loader"></div>
|
||||
<span>Provádí se změna nastavení, prosím čekejte...</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
19
basic/srv/spotter/wsgi.py
Executable file
@ -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)
|
@ -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)
|
@ -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
|
||||
|
1
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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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.
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -11,5 +11,5 @@ listen = *:8080
|
||||
ssl_webdav = False
|
||||
|
||||
[email]
|
||||
from = gnuhealth@spotter.ngo
|
||||
from = admin@example.com
|
||||
uri = smtp://postfix:25
|
||||
|
@ -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');
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -15,7 +15,7 @@ EMAIL_HOST = 'postfix'
|
||||
|
||||
XACCELREDIRECT = True
|
||||
|
||||
GOOGLE_API_KEY = 'AIzaSyBvIF3D550tlpL6o1xRrDurGo-81VhHlOw'
|
||||
GOOGLE_API_KEY = ''
|
||||
|
||||
DEBUG = True
|
||||
JSON_DEBUG = DEBUG
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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' <sahanademo@spotter.ngo>"
|
||||
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 = ""
|
||||
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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' <sahana@spotter.ngo>"
|
||||
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 = ""
|
||||
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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' <sambro@spotter.ngo>"
|
||||
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 = ""
|
||||
|
||||
|
@ -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
|
@ -14,7 +14,7 @@
|
||||
</connectors>
|
||||
</authentication>
|
||||
<database dbDriver="pgsql" dbHostname="postgres" dbDatabase="seeddms" dbUser="seeddms" dbPass="${SEEDDMS_PWD}" doNotCheckVersion="false"/>
|
||||
<smtp smtpServer="postfix" smtpPort="25" smtpSendFrom="seeddms@spotter.ngo" smtpUser="" smtpPassword=""/>
|
||||
<smtp smtpServer="postfix" smtpPort="25" smtpSendFrom="admin@example.com" smtpUser="" smtpPassword=""/>
|
||||
</system>
|
||||
<advanced>
|
||||
<display siteDefaultPage="" rootFolderID="1" titleDisplayHack="true" showMissingTranslations="false"/>
|
||||
|
@ -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=
|
||||
|
@ -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
|
||||
|
@ -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": ""
|
||||
}
|
||||
|
@ -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
|