Compare commits
No commits in common. "master" and "last-docker" have entirely different histories.
master
...
last-docke
13
.gitlab/issue_templates/Issue.md
Normal file
@ -0,0 +1,13 @@
|
||||
### Steps to reproduce
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
### Expected behaviour
|
||||
|
||||
|
||||
### Observed behaviour
|
||||
|
||||
|
||||
### Additional data (ticket URL, log, timestamp, stack trace etc.)
|
||||
|
6
.gitmodules
vendored
@ -1,6 +0,0 @@
|
||||
[submodule "app-vmmgr"]
|
||||
path = apk/vmmgr
|
||||
url = ssh://git@git.spotter.cz:2222/Spotter-Cluster/vmmgr.git
|
||||
[submodule "spoc"]
|
||||
path = apk/spoc
|
||||
url = ssh://git@git.spotter.cz:2222/Spotter-Cluster/spoc.git
|
40
00-install.sh
Executable file
@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# For production builds change to DEBUG=0 or comment the line entirely
|
||||
export DEBUG=1
|
||||
|
||||
SOURCE_DIR=$(realpath $(dirname "${0}"))
|
||||
|
||||
# Install shared packages and perform OS customization
|
||||
${SOURCE_DIR}/basic.sh
|
||||
${SOURCE_DIR}/basic-runtimes.sh
|
||||
|
||||
# Install applications
|
||||
${SOURCE_DIR}/ckan.sh
|
||||
# ${SOURCE_DIR}/crisiscleanup.sh
|
||||
# ${SOURCE_DIR}/cts.sh
|
||||
${SOURCE_DIR}/frontlinesms.sh
|
||||
${SOURCE_DIR}/gnuhealth.sh
|
||||
${SOURCE_DIR}/kanboard.sh
|
||||
${SOURCE_DIR}/mifosx.sh
|
||||
${SOURCE_DIR}/motech.sh
|
||||
${SOURCE_DIR}/opendatakit-build.sh
|
||||
${SOURCE_DIR}/opendatakit.sh
|
||||
${SOURCE_DIR}/openmapkit.sh
|
||||
${SOURCE_DIR}/pandora.sh
|
||||
${SOURCE_DIR}/sahana.sh
|
||||
${SOURCE_DIR}/sahana-demo.sh
|
||||
${SOURCE_DIR}/sambro.sh
|
||||
${SOURCE_DIR}/seeddms.sh
|
||||
${SOURCE_DIR}/sigmah.sh
|
||||
${SOURCE_DIR}/ushahidi.sh
|
||||
|
||||
# Perform cleanup only if DEBUG mode is not set
|
||||
[ ${DEBUG:-0} -eq 1 ] && exit 0
|
||||
# Clean package cache
|
||||
rm -rf /var/cache/apk/*
|
||||
# Remove root settings
|
||||
find /root -mindepth 1 -maxdepth 1 | xargs rm -rf
|
||||
# Change root password
|
||||
passwd
|
90
README.md
Normal file
@ -0,0 +1,90 @@
|
||||
This is the main repository containing all installation scripts, configuration and customization of operating system all application present in Spotter Cluster virtual machine.
|
||||
|
||||
## Virtual machine specifications
|
||||
- **Memory:** 4 GB
|
||||
- **CPU:** 1 processor, 2 cores
|
||||
- **Hard Disk:** SCSI, 60 GB
|
||||
|
||||
In case you're setting up a VMWare virtual machine, select OS type *Other Linux 3.x kernel 64-bit* and after you create the VM, manually edit the `*.vmx` file using a text editor and set there `mem.hotadd = "FALSE"`. Failing to do so will result in system unable to boot. Other hypervizors don't need this adjustment.
|
||||
|
||||
## Operating system installation
|
||||
|
||||
Download **Alpine Virtual 3.8.0 x86_64** from <https://alpinelinux.org/downloads/> and boot from it. At the login prompt, use the root user without password to log in.
|
||||
|
||||
```
|
||||
# Set up interfaces (leave the default choices)
|
||||
setup-interfaces
|
||||
ifup eth0
|
||||
|
||||
# Download and launch the setup script
|
||||
wget dl.dasm.cz/alpine.sh
|
||||
sh alpine.sh
|
||||
```
|
||||
|
||||
The script will perform the bare Alpine linux installation into VM using LUKS-on-LVM. The default disk encryption password is `password`. No root password is set.
|
||||
|
||||
## Application installation
|
||||
|
||||
### First time setup
|
||||
```
|
||||
# Install git
|
||||
apk --no-cache add git
|
||||
|
||||
# Clone the repository
|
||||
git clone https://gitlab.dasm.cz/Spotter-Cluster/Spotter-Cluster.git
|
||||
|
||||
# Enter the repository directory
|
||||
cd Spotter-Cluster
|
||||
|
||||
# Optional: Edit the install sequence script
|
||||
vi 00-install.sh
|
||||
|
||||
# Launch the script
|
||||
./00-install.sh
|
||||
```
|
||||
|
||||
### Resuming from a snapshot
|
||||
Assumes that the repository has already been cloned.
|
||||
```
|
||||
# Enter the repository directory
|
||||
cd Spotter-Cluster
|
||||
|
||||
# Update repository
|
||||
git pull
|
||||
|
||||
# Optional: Edit the install sequence script
|
||||
vi 00-install.sh
|
||||
|
||||
# Launch the script
|
||||
./00-install.sh
|
||||
```
|
||||
|
||||
## Host assignment
|
||||
|
||||
| Application | Container | UID/GID | Internal Port | Host |
|
||||
|-----------------|------------------:|--------:|-----------------:|----------|
|
||||
| ActiveMQ | activemq | 61616 | 61616 (ActiveMQ) | - |
|
||||
| CKAN | ckan | 8003 | 8080 (HTTP) | ckan |
|
||||
| CKAN Datapusher | ckan-datapusher | 8004 | 8080 (HTTP) | - |
|
||||
| Crisis Cleanup | crisiscleanup | 8005 | 8080 (HTTP) | cc |
|
||||
| CTS | cts | 8006 | 8080 (HTTP) | cts |
|
||||
| FrontlineSMS | frontlinesms | 8018 | 8080 (HTTP) | sms |
|
||||
| GNU Health | gnuhealth | 8008 | 8080 (HTTP) | gh |
|
||||
| KanBoard | kanboard | 8009 | 8080 (HTTP) | kb |
|
||||
| MariaDB | mariadb | 3306 | 3306 (MySQL) | - |
|
||||
| Mifos X | mifosx | 8012 | 8080 (HTTP) | mifosx |
|
||||
| Motech | motech | 8013 | 8080 (HTTP) | motech |
|
||||
| ODK Aggregate | opendatakit | 8015 | 8080 (HTTP) | odk |
|
||||
| ODK Build | opendatakit-build | 8017 | 8080 (HTTP) | odkbuild |
|
||||
| OpenMapKit | openmapkit | 8007 | 8080 (HTTP) | omk |
|
||||
| Pan.do/ra | pandora | 8002 | 8080 (HTTP) | pandora |
|
||||
| Postfix | postfix | 587 | 25 (SMTP) | - |
|
||||
| Postgres | postgres | 5432 | 5432 (Postgres) | - |
|
||||
| RabbitMQ | rabbitmq | 5672 | 5672 (AMQP) | - |
|
||||
| Redis | redis | 6379 | 6379 (Redis) | - |
|
||||
| Sahana | sahana | 8001 | 8080 (HTTP) | sahana |
|
||||
| SAMBRO | sambro | 8001 | 8080 (HTTP) | sambro |
|
||||
| SeedDMS | seeddms | 8010 | 8080 (HTTP) | dms |
|
||||
| Sigmah | sigmah | 8011 | 8080 (HTTP) | sigmah |
|
||||
| Solr | solr | 8983 | 8983 (HTTP) | - |
|
||||
| Ushahidi | ushahidi | 8014 | 8080 (HTTP) | ush |
|
13
activemq.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
SOURCE_DIR=$(realpath $(dirname "${0}"))/activemq
|
||||
|
||||
# Build Docker container
|
||||
docker build -t activemq ${SOURCE_DIR}
|
||||
cp ${SOURCE_DIR}/etc/init.d/activemq /etc/init.d/activemq
|
||||
rc-update -u
|
||||
|
||||
# Configure ActiveMQ
|
||||
mkdir -p /srv/activemq/data
|
||||
chown -R 61616:61616 /srv/activemq/data
|
23
activemq/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
FROM java
|
||||
LABEL maintainer="Disassembler <disassembler@dasm.cz>"
|
||||
|
||||
RUN \
|
||||
# Download and install ActiveMQ
|
||||
wget http://archive.apache.org/dist/activemq/5.15.5/apache-activemq-5.15.5-bin.tar.gz -O /tmp/activemq.tgz \
|
||||
&& tar xf /tmp/activemq.tgz -C /srv \
|
||||
&& mv /srv/apache-activemq-5.15.5 /srv/activemq \
|
||||
&& rm -f /tmp/activemq.tgz \
|
||||
# Create OS user
|
||||
&& addgroup -S -g 61616 activemq \
|
||||
&& adduser -S -u 61616 -h /srv/activemq -s /bin/false -g activemq -G activemq activemq \
|
||||
&& mkdir /srv/activemq/tmp \
|
||||
&& chown activemq:activemq /srv/activemq/tmp \
|
||||
# Configure Java heap size
|
||||
&& sed -i "s/-Xms64M -Xmx1G/-Xms32M -Xmx256M/" /srv/activemq/bin/env
|
||||
|
||||
COPY docker/ /
|
||||
|
||||
VOLUME ["/srv/activemq/data"]
|
||||
EXPOSE 61616
|
||||
|
||||
CMD ["s6-svscan", "/etc/services.d"]
|
0
extra/graveyard/crisiscleanup3/docker/etc/services.d/.s6-svscan/finish → activemq/docker/etc/services.d/.s6-svscan/finish
Normal file → Executable file
5
activemq/docker/etc/services.d/activemq/run
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/execlineb -P
|
||||
|
||||
fdmove -c 2 1
|
||||
s6-setuidgid 61616:61616
|
||||
/srv/activemq/bin/activemq console
|
19
activemq/etc/init.d/activemq
Executable file
@ -0,0 +1,19 @@
|
||||
#!/sbin/openrc-run
|
||||
|
||||
description="ActiveMQ docker container"
|
||||
|
||||
depend() {
|
||||
need docker
|
||||
}
|
||||
|
||||
start() {
|
||||
/usr/bin/docker run -d --rm \
|
||||
--name activemq \
|
||||
-h activemq \
|
||||
-v /srv/activemq/data:/srv/activemq/data \
|
||||
activemq
|
||||
}
|
||||
|
||||
stop() {
|
||||
/usr/bin/docker stop activemq
|
||||
}
|
100
alpine.sh
Executable file
@ -0,0 +1,100 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Based on
|
||||
# https://wiki.alpinelinux.org/wiki/LVM_on_LUKS
|
||||
|
||||
# Prerequisites for this script
|
||||
# setup-interfaces
|
||||
# ifup eth0
|
||||
|
||||
# Set up repositories
|
||||
cat <<EOF >/etc/apk/repositories
|
||||
http://dl-cdn.alpinelinux.org/alpine/v3.8/main
|
||||
http://dl-cdn.alpinelinux.org/alpine/v3.8/community
|
||||
#http://dl-cdn.alpinelinux.org/alpine/edge/main
|
||||
#http://dl-cdn.alpinelinux.org/alpine/edge/community
|
||||
#http://dl-cdn.alpinelinux.org/alpine/edge/testing
|
||||
EOF
|
||||
|
||||
# Install disk management tools
|
||||
apk --no-cache add lvm2 cryptsetup e2fsprogs syslinux
|
||||
|
||||
# Create disk partitions
|
||||
cat <<EOF | fdisk /dev/sda
|
||||
n
|
||||
p
|
||||
1
|
||||
|
||||
+100m
|
||||
a
|
||||
1
|
||||
n
|
||||
p
|
||||
2
|
||||
|
||||
|
||||
t
|
||||
2
|
||||
8e
|
||||
w
|
||||
EOF
|
||||
|
||||
# Set up partition encryption
|
||||
echo -n 'password' | cryptsetup -q luksFormat /dev/sda2
|
||||
echo -n 'password' | cryptsetup open --type luks /dev/sda2 system
|
||||
|
||||
# Set up LVM
|
||||
pvcreate /dev/mapper/system
|
||||
vgcreate vg0 /dev/mapper/system
|
||||
lvcreate -l 100%FREE vg0 -n root
|
||||
|
||||
# Format
|
||||
mkfs.ext4 -m0 /dev/sda1
|
||||
mkfs.ext4 -m1 /dev/vg0/root
|
||||
|
||||
# Mount
|
||||
mount -t ext4 /dev/vg0/root /mnt
|
||||
mkdir /mnt/boot
|
||||
mount -t ext4 /dev/sda1 /mnt/boot
|
||||
|
||||
# Install Alpine linux
|
||||
setup-disk -m sys /mnt
|
||||
|
||||
# Update boot-time volume information
|
||||
BOOT_UUID=$(blkid /dev/sda1 | cut -d' ' -f2 | tr -d '"')
|
||||
cat <<EOF >/mnt/etc/fstab
|
||||
/dev/vg0/root / ext4 rw,noatime,data=ordered 0 1
|
||||
${BOOT_UUID} /boot ext4 rw,noatime,data=ordered 0 2
|
||||
/dev/vg0/swap swap swap defaults 0 0
|
||||
EOF
|
||||
echo "system /dev/sda2 none luks" >/mnt/etc/crypttab
|
||||
|
||||
# Rebuild initfs
|
||||
sed -i 's/lvm/lvm cryptsetup/' /mnt/etc/mkinitfs/mkinitfs.conf
|
||||
mkinitfs -c /mnt/etc/mkinitfs/mkinitfs.conf -b /mnt $(ls /mnt/lib/modules)
|
||||
|
||||
# Update extlinux (ignore the errors)
|
||||
sed -i 's/rootfstype=ext4/rootfstype=ext4 cryptroot=\/dev\/sda2 cryptdm=system/' /mnt/etc/update-extlinux.conf
|
||||
chroot /mnt update-extlinux
|
||||
|
||||
# Set time zone
|
||||
chroot /mnt setup-timezone -z Europe/Prague
|
||||
|
||||
# Set hostname
|
||||
echo 'spotter.vm' >/mnt/etc/hostname
|
||||
echo '127.0.0.1 spotter.vm localhost localhost.localdomain' >/mnt/etc/hosts
|
||||
sed -i '/hostname/d' /mnt/etc/network/interfaces
|
||||
|
||||
# Enable services on boot
|
||||
ln -s /etc/init.d/networking /mnt/etc/runlevels/boot
|
||||
ln -s /etc/init.d/urandom /mnt/etc/runlevels/boot
|
||||
|
||||
# Install bootloader to MBR
|
||||
dd bs=440 count=1 conv=notrunc if=/mnt/usr/share/syslinux/mbr.bin of=/dev/sda
|
||||
|
||||
# Unmount and shut down
|
||||
umount /mnt/boot
|
||||
umount /mnt
|
||||
vgchange -a n
|
||||
cryptsetup luksClose system
|
||||
poweroff
|
@ -1,28 +0,0 @@
|
||||
# Maintainer: Disassembler <disassembler@dasm.cz>
|
||||
pkgname=py3-secure-cookie
|
||||
_pkgname=secure-cookie
|
||||
pkgver=0.1.0
|
||||
pkgrel=0
|
||||
pkgdesc="Secure cookie and session interface for WSGI applications"
|
||||
url="https://secure-cookie.readthedocs.io/"
|
||||
arch="noarch"
|
||||
license="MIT"
|
||||
depends="python3"
|
||||
makedepends="py3-setuptools"
|
||||
checkdepends="py3-pytest py3-werkzeug"
|
||||
source="https://files.pythonhosted.org/packages/source/${_pkgname:0:1}/$_pkgname/$_pkgname-$pkgver.tar.gz"
|
||||
builddir="$srcdir/$_pkgname-$pkgver"
|
||||
|
||||
build() {
|
||||
python3 setup.py build
|
||||
}
|
||||
|
||||
package() {
|
||||
python3 setup.py install --prefix=/usr --root="$pkgdir"
|
||||
}
|
||||
|
||||
check() {
|
||||
PYTHONPATH=$PWD/build/lib pytest
|
||||
}
|
||||
|
||||
sha512sums="2e57dba6f73ceb03eda33c804dbe2277c9fe700dd1be219bb3d8d43a5c9105c2323fb6b28d74d3a1dfc8fbbd938b91ab54d3e1bac1dc74490335b1d27e43b55a secure-cookie-0.1.0.tar.gz"
|
1
apk/spoc
@ -1 +0,0 @@
|
||||
Subproject commit 8c22df2e71de329a286e75af9bff69e82876db35
|
@ -1 +0,0 @@
|
||||
Subproject commit 1c810db9472f50bd9dbe1e0f38df72590b120124
|
12
basic-runtimes.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
SOURCE_DIR=$(realpath $(dirname "${0}"))/basic-runtimes
|
||||
|
||||
# Build Docker images
|
||||
docker build -t java ${SOURCE_DIR}/java
|
||||
docker build -t php ${SOURCE_DIR}/php
|
||||
docker build -t python2 ${SOURCE_DIR}/python2
|
||||
docker build -t python3 ${SOURCE_DIR}/python3
|
||||
docker build -t ruby ${SOURCE_DIR}/ruby
|
||||
docker build -t tomcat ${SOURCE_DIR}/tomcat
|
6
basic-runtimes/java/Dockerfile
Normal file
@ -0,0 +1,6 @@
|
||||
FROM alpine
|
||||
LABEL maintainer="Disassembler <disassembler@dasm.cz>"
|
||||
|
||||
RUN \
|
||||
# Install Java 1.8 JRE
|
||||
apk --no-cache add openjdk8-jre-base
|
6
basic-runtimes/php/Dockerfile
Normal file
@ -0,0 +1,6 @@
|
||||
FROM alpine
|
||||
LABEL maintainer="Disassembler <disassembler@dasm.cz>"
|
||||
|
||||
RUN \
|
||||
# Install PHP runtime
|
||||
apk --no-cache add nginx php7 php7-ctype php7-fpm php7-gd php7-json php7-mbstring php7-mcrypt php7-opcache php7-session
|
10
basic-runtimes/python2/Dockerfile
Normal file
@ -0,0 +1,10 @@
|
||||
FROM alpine
|
||||
LABEL maintainer="Disassembler <disassembler@dasm.cz>"
|
||||
|
||||
RUN \
|
||||
# Install XML libs
|
||||
apk --no-cache add libxml2 libxslt
|
||||
|
||||
RUN \
|
||||
# Install Python2 runtime
|
||||
apk --no-cache add python2
|
11
basic-runtimes/python3/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
||||
FROM alpine
|
||||
LABEL maintainer="Disassembler <disassembler@dasm.cz>"
|
||||
|
||||
RUN \
|
||||
# Install XML libs
|
||||
apk --no-cache add libxml2 libxslt
|
||||
|
||||
RUN \
|
||||
# Install Python3 runtime
|
||||
apk --no-cache add python3 \
|
||||
&& ln -s /usr/bin/python3 /usr/bin/python
|
33
basic-runtimes/ruby/Dockerfile
Normal file
@ -0,0 +1,33 @@
|
||||
FROM alpine
|
||||
LABEL maintainer="Disassembler <disassembler@dasm.cz>"
|
||||
|
||||
RUN \
|
||||
# Install Ruby runtime dependencies
|
||||
apk --no-cache add gdbm libressl readline zlib
|
||||
|
||||
RUN \
|
||||
# Install Ruby build dependencies
|
||||
apk --no-cache add --virtual .deps build-base autoconf gdbm-dev libressl-dev linux-headers readline-dev zlib-dev \
|
||||
# Download and unpack Ruby
|
||||
&& wget http://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.6.tar.xz -O ruby.tar.xz \
|
||||
&& mkdir -p /usr/src/ruby \
|
||||
&& tar -xJf ruby.tar.xz -C /usr/src/ruby --strip-components=1 \
|
||||
&& rm ruby.tar.xz \
|
||||
&& cd /usr/src/ruby \
|
||||
# Hackfix to suppress "Insecure world writable dir" warning
|
||||
&& sed -ni 'p;13a #define ENABLE_PATH_CHECK 0' file.c \
|
||||
# Configure compilation + hackfix to detect isnan/isinf macros
|
||||
&& autoconf \
|
||||
&& ac_cv_func_isnan=yes ac_cv_func_isinf=yes ./configure --build=x86_64-linux-musl --disable-install-doc --enable-shared \
|
||||
# Compile and install Ruby
|
||||
&& make -j $(nproc) \
|
||||
&& make install \
|
||||
# Install RubyGems and Bundler
|
||||
&& mkdir -p /usr/local/etc \
|
||||
&& echo -e 'install: --no-document\nupdate: --no-document' >/usr/local/etc/gemrc \
|
||||
&& gem update --system \
|
||||
# Cleanup
|
||||
&& cd /tmp \
|
||||
&& rm -r /usr/src/ruby \
|
||||
&& apk --no-cache del .deps \
|
||||
&& rm -rf /root/.gem
|
15
basic-runtimes/tomcat/Dockerfile
Normal file
@ -0,0 +1,15 @@
|
||||
FROM java
|
||||
LABEL maintainer="Disassembler <disassembler@dasm.cz>"
|
||||
|
||||
RUN \
|
||||
# Install Tomcat 8
|
||||
wget http://mirror.hosting90.cz/apache/tomcat/tomcat-8/v8.0.53/bin/apache-tomcat-8.0.53.tar.gz -O /tmp/apache-tomcat-8.tgz \
|
||||
&& tar xf /tmp/apache-tomcat-8.tgz -C /srv \
|
||||
&& mv /srv/apache-tomcat-8.0.53 /srv/tomcat \
|
||||
# Make catalina.sh available globally
|
||||
&& ln -s /srv/tomcat/bin/catalina.sh /usr/bin/catalina.sh \
|
||||
# Cleanup
|
||||
&& rm -rf /srv/tomcat/webapps/ROOT /srv/tomcat/webapps/docs /srv/tomcat/webapps/examples /srv/tomcat/webapps/host-manager /srv/tomcat/webapps/manager \
|
||||
&& rm -f /tmp/apache-tomcat-8.tgz
|
||||
|
||||
COPY docker/ /
|
0
lxc-shared/alpine3.12-tomcat7/image.d/srv/tomcat/bin/setenv.sh → basic-runtimes/tomcat/docker/srv/tomcat/bin/setenv.sh
Normal file → Executable file
70
basic.sh
Executable file
@ -0,0 +1,70 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
SOURCE_DIR=$(realpath $(dirname "${0}"))/basic
|
||||
|
||||
# Install packages
|
||||
apk --no-cache add curl docker e2fsprogs-extra gettext kbd-misc libressl python3 py3-bcrypt py3-cffi py3-dnspython py3-jinja2 py3-requests py3-six py3-werkzeug nginx util-linux
|
||||
if [ ${DEBUG:-0} -eq 1 ]; then
|
||||
# Install some utilities for DEBUG mode
|
||||
apk --no-cache add git file htop less openssh-server openssh-sftp-server
|
||||
fi
|
||||
|
||||
# Copy root profile files and settings for DEBUG mode
|
||||
if [ ${DEBUG:-0} -eq 1 ]; then
|
||||
mkdir -p /root/.config/htop /root/.ssh
|
||||
cp ${SOURCE_DIR}/root/.profile /root/.profile
|
||||
cp ${SOURCE_DIR}/root/.ssh/authorized_keys /root/.ssh/authorized_keys
|
||||
cp ${SOURCE_DIR}/root/.config/htop/htoprc /root/.config/htop/htoprc
|
||||
fi
|
||||
|
||||
# Copy boot configuration
|
||||
cp ${SOURCE_DIR}/boot/extlinux.conf /boot/extlinux.conf
|
||||
cp ${SOURCE_DIR}/boot/vm.txt /boot/vm.txt
|
||||
cp ${SOURCE_DIR}/etc/inittab /etc/inittab
|
||||
cp ${SOURCE_DIR}/sbin/extend-disk /sbin/extend-disk
|
||||
cp ${SOURCE_DIR}/sbin/vmtty /sbin/vmtty
|
||||
>/etc/motd
|
||||
|
||||
# Enable support for Czech characters
|
||||
cp ${SOURCE_DIR}/etc/rc.conf /etc/rc.conf
|
||||
cp ${SOURCE_DIR}/etc/conf.d/consolefont /etc/conf.d/consolefont
|
||||
|
||||
# Configure NTP client
|
||||
cp ${SOURCE_DIR}/etc/conf.d/ntpd /etc/conf.d/ntpd
|
||||
|
||||
# Download and configure acme.sh
|
||||
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
|
||||
chmod +x /usr/bin/acme.sh
|
||||
|
||||
# Copy AppMgr resources
|
||||
cp ${SOURCE_DIR}/etc/init.d/vm-appmgr /etc/init.d/vm-appmgr
|
||||
rc-update -u
|
||||
cp -r ${SOURCE_DIR}/srv/vm /srv/vm
|
||||
ln -s /srv/vm/cli.py /usr/bin/vm-appmgr
|
||||
|
||||
# Create a self-signed certificate
|
||||
vm-appmgr create-selfsigned
|
||||
|
||||
# Configure nginx
|
||||
cp ${SOURCE_DIR}/etc/nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# Configure services
|
||||
for SERVICE in consolefont crond nginx ntpd sshd vm-appmgr swap; do
|
||||
rc-update add ${SERVICE} boot
|
||||
service ${SERVICE} start
|
||||
done
|
||||
|
||||
# Configure Docker service
|
||||
cp ${SOURCE_DIR}/etc/init.d/docker /etc/init.d/docker
|
||||
rc-update -u
|
||||
rc-update add docker
|
||||
service docker start
|
||||
|
||||
# Create basic images
|
||||
docker build -t alpine ${SOURCE_DIR}
|
||||
|
||||
# Set dummy host and generate related files
|
||||
vm-appmgr update-host spotter.vm 443
|
6
basic/Dockerfile
Normal file
@ -0,0 +1,6 @@
|
||||
FROM alpine:3.8
|
||||
LABEL maintainer="Disassembler <disassembler@dasm.cz>"
|
||||
|
||||
RUN \
|
||||
# Install S6 supervisor
|
||||
apk --no-cache add s6
|
9
basic/boot/extlinux.conf
Normal file
@ -0,0 +1,9 @@
|
||||
DEFAULT vm
|
||||
PROMPT 0
|
||||
ALLOWOPTIONS 0
|
||||
NOESCAPE 1
|
||||
DISPLAY vm.txt
|
||||
LABEL vm
|
||||
LINUX vmlinuz-virt
|
||||
INITRD initramfs-virt
|
||||
APPEND root=/dev/vg0/root modules=sd-mod,usb-storage,ext4 nomodeset quiet rootfstype=ext4 cryptroot=/dev/sda2 cryptdm=system
|
1
basic/etc/conf.d/ntpd
Normal file
@ -0,0 +1 @@
|
||||
NTPD_OPTS="-N -p tik.cesnet.cz -p tak.cesnet.cz"
|
40
basic/etc/init.d/docker
Executable file
@ -0,0 +1,40 @@
|
||||
#!/sbin/openrc-run
|
||||
# Copyright 1999-2013 Gentoo Foundation
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
|
||||
command="${DOCKERD_BINARY:-/usr/bin/dockerd}"
|
||||
pidfile="${DOCKER_PIDFILE:-/run/${RC_SVCNAME}.pid}"
|
||||
command_args="-p \"${pidfile}\" ${DOCKER_OPTS}"
|
||||
DOCKER_LOGFILE="${DOCKER_LOGFILE:-/var/log/${RC_SVCNAME}.log}"
|
||||
DOCKER_ERRFILE="${DOCKER_ERRFILE:-${DOCKER_LOGFILE}}"
|
||||
DOCKER_OUTFILE="${DOCKER_OUTFILE:-${DOCKER_LOGFILE}}"
|
||||
start_stop_daemon_args="--background \
|
||||
--stderr \"${DOCKER_ERRFILE}\" --stdout \"${DOCKER_OUTFILE}\""
|
||||
|
||||
grsecdir=/proc/sys/kernel/grsecurity
|
||||
|
||||
depend() {
|
||||
need sysfs cgroups
|
||||
}
|
||||
|
||||
start_pre() {
|
||||
checkpath -f -m 0644 -o root:docker "$DOCKER_LOGFILE"
|
||||
|
||||
for i in $disable_grsec; do
|
||||
if [ -e "$grsecdir/$i" ]; then
|
||||
einfo " Disabling $i"
|
||||
echo 0 > "$grsecdir/$i"
|
||||
fi
|
||||
done
|
||||
ulimit -n 1048576
|
||||
|
||||
# Having non-zero limits causes performance problems due to accounting overhead
|
||||
# in the kernel. We recommend using cgroups to do container-local accounting.
|
||||
ulimit -p unlimited
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
start_post() {
|
||||
ewaitfile 10 /var/run/docker.sock
|
||||
}
|
6
basic/etc/init.d/vm-appmgr
Executable file
@ -0,0 +1,6 @@
|
||||
#!/sbin/openrc-run
|
||||
|
||||
command=/srv/vm/wsgi.py
|
||||
description="VM application manager"
|
||||
pidfile=/var/run/vm-appmgr.pid
|
||||
start_stop_daemon_args="--background --make-pidfile --stderr /dev/null --stdout /dev/null"
|
@ -2,10 +2,12 @@
|
||||
|
||||
::sysinit:/sbin/openrc sysinit >/dev/null 2>&1
|
||||
::sysinit:/sbin/openrc boot >/dev/null 2>&1
|
||||
::wait:/sbin/extend-disk >/dev/null 2>&1
|
||||
::wait:/sbin/openrc default >/dev/null 2>&1
|
||||
|
||||
# Set up getty
|
||||
tty1::respawn:/sbin/getty -n -l /sbin/vmtty 38400 tty1
|
||||
ttyS0::respawn:/sbin/getty -L 115200 ttyS0 xterm
|
||||
|
||||
# Stuff to do for the 3-finger salute
|
||||
::ctrlaltdel:/sbin/reboot
|
@ -15,17 +15,15 @@ http {
|
||||
server_tokens off;
|
||||
client_max_body_size 100m;
|
||||
sendfile on;
|
||||
tcp_nodelay on;
|
||||
gzip_vary on;
|
||||
charset utf-8;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_protocols TLSv1.2;
|
||||
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_certificate /etc/ssl/services.pem;
|
||||
ssl_certificate_key /etc/ssl/services.key;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:1m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"';
|
||||
access_log /var/log/nginx/access.log main;
|
@ -1,3 +1,2 @@
|
||||
rc_cgroup_mode=unified
|
||||
rc_tty_number=1
|
||||
unicode="YES"
|
||||
rc_tty_number=1
|
2
basic/root/.profile
Normal file
@ -0,0 +1,2 @@
|
||||
alias ll="ls -la"
|
||||
alias view="vi"
|
1
basic/root/.ssh/authorized_keys
Normal file
@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILc3Mu7OlKrV7VqDQZ31vT3I3JJxtNNBiemUTRQVOZ3I Disassembler
|
39
basic/sbin/extend-disk
Executable file
@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# No resizing with less than 10k unused blocks
|
||||
BLOCKS_FREE=$(/usr/bin/awk '/sda$/ {blocks = $3} /sda\d/ {blocks -= $3} END {print blocks}' /proc/partitions)
|
||||
[ ${BLOCKS_FREE} -lt 10240 ] && exit 0
|
||||
|
||||
# Resize physical partition
|
||||
# Force busybox fdisk as util-linux fdisk breaks subsequent partx command
|
||||
cat <<EOF | /bin/busybox fdisk /dev/sda || /bin/true
|
||||
d
|
||||
2
|
||||
n
|
||||
p
|
||||
2
|
||||
|
||||
|
||||
t
|
||||
2
|
||||
8e
|
||||
w
|
||||
EOF
|
||||
|
||||
# Re-read partition table
|
||||
/usr/sbin/partx -u /dev/sda2
|
||||
|
||||
# Resize dmcrypt and LVM PV
|
||||
/sbin/cryptsetup resize system
|
||||
/sbin/pvresize /dev/mapper/system
|
||||
|
||||
# Create swap if it doesn't exist
|
||||
if [ ! -e /dev/vg0/swap ]; then
|
||||
/sbin/lvcreate -L 4G vg0 -n swap
|
||||
/sbin/mkswap /dev/vg0/swap
|
||||
fi
|
||||
|
||||
# Extend LV and underlying filesystem
|
||||
/sbin/lvextend -l +100%FREE vg0/root
|
||||
/usr/sbin/resize2fs /dev/vg0/root
|
8
basic/sbin/vmtty
Executable file
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Rebuild /etc/issue
|
||||
/usr/bin/vm-appmgr rebuild-issue
|
||||
# Remove double-escaping needed for the real /etc/issue
|
||||
/bin/sed 's|\\\\|\\|g' /etc/issue
|
||||
# Wait for key press
|
||||
read a
|
344
basic/srv/vm/appmgr/__init__.py
Normal file
@ -0,0 +1,344 @@
|
||||
# -*- 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/vm/config.json'
|
||||
ISSUE_FILE = '/etc/issue'
|
||||
NGINX_DIR = '/etc/nginx/conf.d'
|
||||
ACME_CRON = '/etc/periodic/daily/acme-sh'
|
||||
CERT_PUB_FILE = '/etc/ssl/services.pem'
|
||||
CERT_KEY_FILE = '/etc/ssl/services.key'
|
||||
CERT_SAN_FILE = '/etc/ssl/san.cnf'
|
||||
|
||||
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 /502.html;
|
||||
location = /502.html {{
|
||||
root /srv/vm/templates;
|
||||
}}
|
||||
|
||||
location = /vm-ping {{
|
||||
add_header Content-Type text/plain;
|
||||
return 200 "vm-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 = /vm-ping {{
|
||||
add_header Content-Type text/plain;
|
||||
return 200 "vm-pong";
|
||||
}}
|
||||
}}
|
||||
|
||||
server {{
|
||||
listen [::]:{port} ssl http2 default_server ipv6only=off;
|
||||
|
||||
location / {{
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}}
|
||||
|
||||
location /static {{
|
||||
root /srv/vm;
|
||||
}}
|
||||
|
||||
error_page 502 /502.html;
|
||||
location = /502.html {{
|
||||
root /srv/vm/templates;
|
||||
}}
|
||||
|
||||
location = /vm-ping {{
|
||||
add_header Content-Type text/plain;
|
||||
return 200 "vm-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[1m{url}\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
|
||||
'''
|
||||
|
||||
CERT_SAN = '''[ req ]
|
||||
distinguished_name = dn
|
||||
x509_extensions = ext
|
||||
[ dn ]
|
||||
[ ext ]
|
||||
subjectAltName=DNS:{domain},DNS:*.{domain}"
|
||||
'''
|
||||
|
||||
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.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_nginx()
|
||||
|
||||
def rebuild_issue(self):
|
||||
# Compile the HTTPS host displayed in terminal banner
|
||||
domain = self.domain
|
||||
# If the dummy host is used, take an IP address of a primary interface instead
|
||||
if domain == 'spotter.vm':
|
||||
domain = tools.get_local_ipv4()
|
||||
if not domain:
|
||||
domain = tools.get_local_ipv6()
|
||||
if not domain:
|
||||
domain = '127.0.0.1'
|
||||
# Rebuild the terminal banner
|
||||
with open(ISSUE_FILE, 'w') as f:
|
||||
f.write(ISSUE_TEMPLATE.format(url=tools.compile_url(domain, self.port)))
|
||||
|
||||
def update_apps_urls(self):
|
||||
# Update configuration for respective applications
|
||||
confupdater.update_url(tools.compile_url(self.domain, self.port, None))
|
||||
# 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 update_password(self, oldpassword, newpassword):
|
||||
# Update LUKS password and adminpwd for WSGI application
|
||||
tools.update_luks_password(oldpassword, newpassword)
|
||||
self.conf['host']['adminpwd'] = tools.adminpwd_hash(newpassword)
|
||||
# Save config to file
|
||||
self.save_conf()
|
||||
|
||||
def create_selfsigned(self):
|
||||
# Create selfsigned certificate with wildcard alternative subject name
|
||||
with open(os.path.join(CERT_SAN_FILE), 'w') as f:
|
||||
f.write(CERT_SAN.format(domain=self.domain))
|
||||
subprocess.run(['openssl', 'req', '-config', CERT_SAN_FILE, '-x509', '-new', '-out', CERT_PUB_FILE, '-keyout', CERT_KEY_FILE, '-nodes', '-days', '7305', '-subj', '/CN={}'.format(self.domain)], check=True)
|
||||
os.chmod(CERT_KEY_FILE, 0o640)
|
||||
|
||||
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()
|
138
basic/srv/vm/appmgr/confupdater.py
Normal file
@ -0,0 +1,138 @@
|
||||
# -*- 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):
|
||||
maria_started = tools.is_service_started('mariadb')
|
||||
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 app_exists(app):
|
||||
return os.path.exists(os.path.join('/srv/', app))
|
||||
|
||||
def update_gmaps_api_key(api_key):
|
||||
# CKAN
|
||||
if app_exists('ckan'):
|
||||
replace_file_line('/srv/ckan/conf/ckan.ini', 'ckanext.geoview.gapi_key = ', api_key)
|
||||
# Crisis Cleanup
|
||||
if app_exists('crisiscleanup'):
|
||||
replace_file_line('/srv/crisiscleanup/conf/boot.rb', 'ENV[\'GOOGLE_MAPS_API_KEY\'] = ', api_key)
|
||||
# Pan.do/ra
|
||||
if app_exists('pandora'):
|
||||
replace_file_line('/srv/pandora/conf/local_settings.py', 'GOOGLE_API_KEY = ', '\'{}\''.format(api_key))
|
||||
# Sahana
|
||||
if app_exists('sahana'):
|
||||
replace_file_line('/srv/sahana/conf/000_config.py', 'settings.gis.api_google = ', '"{}"'.format(api_key))
|
||||
# Sahana Demo
|
||||
if app_exists('sahana-demo'):
|
||||
replace_file_line('/srv/sahana-demo/conf/000_config.py', 'settings.gis.api_google = ', '"{}"'.format(api_key))
|
||||
# SAMBRO
|
||||
if app_exists('sambro'):
|
||||
replace_file_line('/srv/sambro/conf/000_config.py', 'settings.gis.api_google = ', '"{}"'.format(api_key))
|
||||
# Sigmah
|
||||
if app_exists('sigmah'):
|
||||
replace_file_line('/srv/sigmah/conf/sigmah.properties', 'maps.key=', api_key)
|
||||
# Ushahidi
|
||||
if app_exists('ushahidi'):
|
||||
replace_file_line('/srv/ushahidi/conf/config.json', ' "google_analytics_id": ', '"{}"'.format(api_key))
|
||||
|
||||
def update_email(email):
|
||||
# CKAN
|
||||
if app_exists('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
|
||||
if app_exists('crisiscleanup'):
|
||||
replace_file_line('/srv/crisiscleanup/conf/initializers/devise.rb', ' config.mailer_sender = ', '\'{}\''.format(email))
|
||||
# CTS
|
||||
if app_exists('cts'):
|
||||
replace_file_line('/srv/cts/conf/spotter.py', 'SERVER_EMAIL = ', '\'{}\''.format(email))
|
||||
# GNU Health
|
||||
if app_exists('gnuhealth'):
|
||||
replace_file_line('/srv/gnuhealth/conf/trytond.conf', 'from = ', email)
|
||||
# KanBoard
|
||||
if app_exists('kanboard'):
|
||||
replace_file_line('/srv/kanboard/conf/config.php', 'define(\'MAIL_FROM\', ', '\'{}\');'.format(email))
|
||||
# Mifos X
|
||||
if app_exists('mifosx'):
|
||||
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
|
||||
if app_exists('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
|
||||
if app_exists('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
|
||||
if app_exists('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
|
||||
if app_exists('seeddms'):
|
||||
replace_file_line('/srv/seeddms/conf/settings.xml', ' <smtp smtpServer="postfix" smtpPort="25" smtpSendFrom=', '"{}" smtpUser="" smtpPassword=""/>'.format(email))
|
||||
# Sigmah
|
||||
if app_exists('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
|
||||
if app_exists('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\\"}}'.format(email, email, email)
|
||||
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')
|
||||
query = 'UPDATE `config` SET `config_value` = "\\"{}\\"" WHERE `group_name` LIKE "site" AND `config_key` LIKE "email";'.format(email)
|
||||
run_mysql_query(query, 'ushahidi')
|
||||
|
||||
def update_url(host):
|
||||
# CKAN
|
||||
if app_exists('ckan'):
|
||||
replace_file_line('/srv/ckan/conf/ckan.ini', 'ckan.site_url = ', 'https://ckan.{}'.format(host))
|
||||
# Motech
|
||||
if app_exists('motech'):
|
||||
replace_file_line('/srv/motech/conf/config/motech-settings.properties', 'server.url=', 'https://motech.{}'.format(host))
|
||||
# Pan.do/ra
|
||||
if app_exists('pandora'):
|
||||
replace_file_line('/srv/pandora/conf/config.jsonc', ' "url": ', '"pandora.{}"'.format(host))
|
||||
# Sahana
|
||||
if app_exists('sahana'):
|
||||
replace_file_line('/srv/sahana/conf/000_config.py', 'settings.base.public_url = ', '"https://sahana.{}"'.format(host))
|
||||
# Sahana Demo
|
||||
if app_exists('sahana-demo'):
|
||||
replace_file_line('/srv/sahana-demo/conf/000_config.py', 'settings.base.public_url = ', '"https://sahana-demo.{}"'.format(host))
|
||||
# SAMBRO
|
||||
if app_exists('sambro'):
|
||||
replace_file_line('/srv/sambro/conf/000_config.py', 'settings.base.public_url = ', '"https://sambro.{}"'.format(host))
|
||||
# Ushahidi
|
||||
if app_exists('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\\"'.format(host)
|
||||
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')
|
124
basic/srv/vm/appmgr/tools.py
Normal file
@ -0,0 +1,124 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import bcrypt
|
||||
import dns.exception
|
||||
import dns.resolver
|
||||
import os
|
||||
import requests
|
||||
import socket
|
||||
import ssl
|
||||
import subprocess
|
||||
|
||||
NULL_IP = '[100::1]'
|
||||
|
||||
def compile_url(domain, port, proto='https'):
|
||||
port = ':{}'.format(port) if (proto == 'https' and port != '443') or (proto == 'http' and port != '80') else ''
|
||||
host = '{}{}'.format(domain, port)
|
||||
return '{}://{}'.format(proto, host) if proto is not None else host
|
||||
|
||||
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('https://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('https://tools.dasm.cz/vm-ping.php', data = {'url': url}, timeout=5).text == 'vm-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 restart_nginx():
|
||||
restart_service('nginx')
|
||||
|
||||
def get_cert_info(cert):
|
||||
data = ssl._ssl._test_decode_cert(cert)
|
||||
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
|
||||
|
||||
def adminpwd_hash(password):
|
||||
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
||||
|
||||
def adminpwd_verify(password, hash):
|
||||
return bcrypt.checkpw(password.encode(), hash.encode())
|
||||
|
||||
def update_luks_password(oldpassword, newpassword):
|
||||
input = '{}\n{}'.format(oldpassword, newpassword).encode()
|
||||
subprocess.run(['cryptsetup', 'luksChangeKey', '/dev/sda2'], input=input, check=True)
|
||||
|
||||
def shutdown_vm():
|
||||
subprocess.run(['/sbin/poweroff'])
|
||||
|
||||
def reboot_vm():
|
||||
subprocess.run(['/sbin/reboot'])
|
28
basic/srv/vm/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]))
|
282
basic/srv/vm/appmgr/wsgiapp.py
Normal file
@ -0,0 +1,282 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from werkzeug.exceptions import BadRequest, HTTPException, NotFound
|
||||
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, CERT_PUB_FILE
|
||||
from . import tools
|
||||
from .validator import InvalidValueException
|
||||
from .wsgilang import WSGILang
|
||||
from .wsgisession import WSGISession
|
||||
|
||||
SESSION_KEY = os.urandom(26)
|
||||
|
||||
class WSGIApp(object):
|
||||
def __init__(self):
|
||||
self.jinja_env = Environment(loader=FileSystemLoader('/srv/vm/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)
|
||||
# Enhance request
|
||||
request.mgr = AppMgr()
|
||||
request.session = WSGISession(request.cookies, SESSION_KEY)
|
||||
request.session.lang = WSGILang()
|
||||
# Dispatch request
|
||||
response = self.dispatch_request(request)
|
||||
# Save session if changed
|
||||
request.session.save(response)
|
||||
return response(environ, start_response)
|
||||
|
||||
def dispatch_request(self, request):
|
||||
adapter = self.get_url_map(request.session).bind_to_environ(request.environ)
|
||||
try:
|
||||
endpoint, values = adapter.match()
|
||||
return getattr(self, endpoint)(request, **values)
|
||||
except NotFound as e:
|
||||
# Return custom 404 page
|
||||
response = self.render_template('404.html', request)
|
||||
response.status_code = 404
|
||||
return response
|
||||
except HTTPException as e:
|
||||
return e
|
||||
|
||||
def get_url_map(self, session):
|
||||
rules = [
|
||||
Rule('/', endpoint='portal_view'),
|
||||
Rule('/login', methods=['GET'], endpoint='login_view'),
|
||||
Rule('/login', methods=['POST'], endpoint='login_action'),
|
||||
Rule('/logout', endpoint='logout_action')
|
||||
]
|
||||
if session['admin']:
|
||||
rules += [
|
||||
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'),
|
||||
Rule('/update-password', endpoint='update_password_action'),
|
||||
Rule('/shutdown-vm', endpoint='shutdown_vm_action'),
|
||||
Rule('/reboot-vm', endpoint='reboot_vm_action'),
|
||||
]
|
||||
return Map(rules)
|
||||
|
||||
def render_template(self, template_name, request, **context):
|
||||
# Enhance context
|
||||
context['conf'] = request.mgr.conf
|
||||
context['session'] = request.session
|
||||
# Render template
|
||||
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 login_view(self, request):
|
||||
return self.render_template('login.html', request)
|
||||
|
||||
def login_action(self, request):
|
||||
password = request.form['password']
|
||||
if tools.adminpwd_verify(password, request.mgr.conf['host']['adminpwd']):
|
||||
request.session['admin'] = True
|
||||
return redirect('/')
|
||||
else:
|
||||
return self.render_template('login.html', request, message=request.session.lang.bad_password())
|
||||
|
||||
def logout_action(self, request):
|
||||
request.session.reset()
|
||||
return redirect('/')
|
||||
|
||||
def portal_view(self, request):
|
||||
# Default view. If domain is set to the default dummy domain, redirects to first-run setup instead.
|
||||
if request.session['admin']:
|
||||
return self.render_template('portal-admin.html', request)
|
||||
return self.render_template('portal-user.html', request)
|
||||
|
||||
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(CERT_PUB_FILE)
|
||||
return self.render_template('setup-host.html', request, 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', request)
|
||||
|
||||
def update_host_action(self, request):
|
||||
# Update domain and port, then restart nginx
|
||||
try:
|
||||
domain = request.form['domain']
|
||||
port = request.form['port']
|
||||
request.mgr.update_host(domain, port, False)
|
||||
server_name = request.environ['HTTP_X_FORWARDED_SERVER_NAME']
|
||||
url = '{}/setup-host'.format(tools.compile_url(server_name, port))
|
||||
response = self.render_json({'ok': request.session.lang.host_updated(url, url)})
|
||||
response.call_on_close(tools.restart_nginx)
|
||||
return response
|
||||
except BadRequest:
|
||||
return self.render_json({'error': request.session.lang.malformed_request()})
|
||||
except InvalidValueException as e:
|
||||
if e.args[0] == 'domain':
|
||||
return self.render_json({'error': request.session.lang.invalid_domain(domain)})
|
||||
if e.args[0] == 'port':
|
||||
return self.render_json({'error': request.session.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
|
||||
mgr = request.mgr
|
||||
domains = [mgr.domain]+['{}.{}'.format(mgr.conf['apps'][app]['host'], mgr.domain) for app in 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': request.session.lang.dns_record_does_not_exist(domain)})
|
||||
if a and a != ipv4:
|
||||
return self.render_json({'error': request.session.lang.dns_record_mismatch(domain, a, ipv4)})
|
||||
if aaaa and aaaa != ipv6:
|
||||
return self.render_json({'error': request.session.lang.dns_record_mismatch(domain, aaaa, ipv6)})
|
||||
except:
|
||||
return self.render_json({'error': request.session.lang.dns_timeout()})
|
||||
return self.render_json({'ok': request.session.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']
|
||||
mgr = request.mgr
|
||||
port = mgr.port if proto == 'https' else '80'
|
||||
domains = [mgr.domain]+['{}.{}'.format(mgr.conf['apps'][app]['host'], mgr.domain) for app in mgr.conf['apps']]
|
||||
for domain in domains:
|
||||
url = tools.compile_url(domain, port, proto)
|
||||
try:
|
||||
if not tools.ping_url(url):
|
||||
return self.render_json({'error': request.session.lang.http_host_not_reachable(url)})
|
||||
except:
|
||||
return self.render_json({'error': request.session.lang.http_timeout()})
|
||||
return self.render_json({'ok': request.session.lang.http_hosts_ok(port)})
|
||||
|
||||
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': request.session.lang.cert_file_missing()})
|
||||
if not request.files['private']:
|
||||
return self.render_json({'error': request.session.lang.key_file_missing()})
|
||||
request.files['public'].save('/tmp/public.pem')
|
||||
request.files['private'].save('/tmp/private.pem')
|
||||
request.mgr.install_cert('/tmp/public.pem', '/tmp/private.pem')
|
||||
os.unlink('/tmp/public.pem')
|
||||
os.unlink('/tmp/private.pem')
|
||||
else:
|
||||
request.mgr.request_cert()
|
||||
except BadRequest:
|
||||
return self.render_json({'error': request.session.lang.malformed_request()})
|
||||
except:
|
||||
return self.render_json({'error': request.session.lang.cert_request_error()})
|
||||
url = tools.compile_url(request.mgr.domain, request.mgr.port)
|
||||
return self.render_json({'ok': request.session.lang.cert_installed(url, url)})
|
||||
|
||||
def update_common_action(self, request):
|
||||
# Update common settings shared between apps - admin e-mail address, Google Maps API key
|
||||
try:
|
||||
request.mgr.update_common(request.form['email'], request.form['gmaps-api-key'])
|
||||
except BadRequest:
|
||||
return self.render_json({'error': request.session.lang.malformed_request()})
|
||||
return self.render_json({'ok': request.session.lang.common_updated()})
|
||||
|
||||
def update_app_visibility_action(self, request):
|
||||
# Update application visibility on portal page
|
||||
try:
|
||||
if request.form['value'] == 'true':
|
||||
request.mgr.show_tiles(request.form['app'])
|
||||
else:
|
||||
request.mgr.hide_tiles(request.form['app'])
|
||||
except (BadRequest, InvalidValueException):
|
||||
return self.render_json({'error': request.session.lang.malformed_request()})
|
||||
return self.render_json({'ok': 'ok'})
|
||||
|
||||
def update_app_autostart_action(self, request):
|
||||
# Update value determining if the app should be automatically started after VM boot
|
||||
try:
|
||||
if request.form['value'] == 'true':
|
||||
request.mgr.enable_autostart(request.form['app'])
|
||||
else:
|
||||
request.mgr.disable_autostart(request.form['app'])
|
||||
except (BadRequest, InvalidValueException):
|
||||
return self.render_json({'error': request.session.lang.malformed_request()})
|
||||
return self.render_json({'ok': 'ok'})
|
||||
|
||||
def start_app_action(self, request):
|
||||
# Starts application along with its dependencies
|
||||
try:
|
||||
request.mgr.start_app(request.form['app'])
|
||||
except (BadRequest, InvalidValueException):
|
||||
return self.render_json({'error': request.session.lang.malformed_request()})
|
||||
except:
|
||||
return self.render_json({'error': request.session.lang.stop_start_error()})
|
||||
return self.render_json({'ok': request.session.lang.app_started()})
|
||||
|
||||
def stop_app_action(self, request):
|
||||
# Stops application along with its dependencies
|
||||
try:
|
||||
request.mgr.stop_app(request.form['app'])
|
||||
except (BadRequest, InvalidValueException):
|
||||
return self.render_json({'error': request.session.lang.malformed_request()})
|
||||
except:
|
||||
return self.render_json({'error': request.session.lang.stop_start_error()})
|
||||
return self.render_json({'ok': request.session.lang.app_stopped()})
|
||||
|
||||
def update_password_action(self, request):
|
||||
# Updates password for both HDD encryption (LUKS-on-LVM) and admin account to vm-appmgr
|
||||
try:
|
||||
if request.form['newpassword'] != request.form['newpassword2']:
|
||||
return self.render_json({'error': request.session.lang.password_mismatch()})
|
||||
if request.form['newpassword'] == '':
|
||||
return self.render_json({'error': request.session.lang.password_empty()})
|
||||
# No need to explicitly validate old password, update_luks_password will raise exception if it's wrong
|
||||
request.mgr.update_password(request.form['oldpassword'], request.form['newpassword'])
|
||||
except:
|
||||
return self.render_json({'error': request.session.lang.bad_password()})
|
||||
return self.render_json({'ok': request.session.lang.password_changed()})
|
||||
|
||||
def reboot_vm_action(self, request):
|
||||
# Reboots VM
|
||||
response = self.render_json({'ok': request.session.lang.reboot_initiated()})
|
||||
response.call_on_close(tools.reboot_vm)
|
||||
return response
|
||||
|
||||
def shutdown_vm_action(self, request):
|
||||
# Shuts down VM
|
||||
response = self.render_json({'ok': request.session.lang.shutdown_initiated()})
|
||||
response.call_on_close(tools.shutdown_vm)
|
||||
return response
|
||||
|
||||
class InvalidRecordException(Exception):
|
||||
pass
|
35
basic/srv/vm/appmgr/wsgilang.py
Normal file
@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
class WSGILang:
|
||||
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. Přejděte na URL <a href="{}">{}</a> nebo 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.',
|
||||
'bad_password': 'Nesprávné heslo',
|
||||
'password_mismatch': 'Zadaná hesla se neshodují',
|
||||
'password_empty': 'Nové heslo nesmí být prázdné',
|
||||
'password_changed': 'Heslo úspěšně změněno',
|
||||
'reboot_initiated': 'Příkaz odeslán. Vyčkejte na restartování virtuálního stroje.',
|
||||
'shutdown_initiated': 'Příkaz odeslán. Vyčkejte na vypnutí virtuálního stroje.',
|
||||
}
|
||||
|
||||
def __getattr__(self, key):
|
||||
def function(*args):
|
||||
return self.lang[key].format(*args)
|
||||
return function
|
32
basic/srv/vm/appmgr/wsgisession.py
Normal file
@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from werkzeug.contrib.securecookie import SecureCookie
|
||||
|
||||
class WSGISession:
|
||||
def __init__(self, cookies, secret_key):
|
||||
self.secret_key = secret_key
|
||||
data = cookies.get('session')
|
||||
if data:
|
||||
self.sc = SecureCookie.unserialize(data, secret_key)
|
||||
else:
|
||||
self.reset()
|
||||
if 'admin' not in self.sc:
|
||||
self.reset()
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.sc.__getitem__(key)
|
||||
def __setitem__(self, key, value):
|
||||
return self.sc.__setitem__(key, value)
|
||||
def __delitem__(self, key):
|
||||
return self.sc.__delitem__(key)
|
||||
def __contains__(self, key):
|
||||
return self.sc.__contains__(key)
|
||||
|
||||
def reset(self):
|
||||
self.sc = SecureCookie(secret_key=self.secret_key)
|
||||
self.sc['admin'] = False
|
||||
|
||||
def save(self, response):
|
||||
if self.sc.should_save:
|
||||
data = self.sc.serialize()
|
||||
response.set_cookie('session', data, httponly=True)
|
114
basic/srv/vm/cli.py
Executable file
@ -0,0 +1,114 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import sys
|
||||
sys.path.append('/srv/vm')
|
||||
|
||||
from appmgr import AppMgr
|
||||
|
||||
parser = argparse.ArgumentParser(description='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_rebuild_issue = subparsers.add_parser('rebuild-issue', help='Rebuilds /etc/issue using current settings - used on VM startup')
|
||||
parser_rebuild_issue.set_defaults(action='rebuild-issue')
|
||||
|
||||
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_update_password = subparsers.add_parser('update-password', help='Updates password for HDD encryption and WSGI administration interface')
|
||||
parser_update_password.set_defaults(action='update-password')
|
||||
|
||||
parser_create_selfsigned = subparsers.add_parser('create-selfsigned', help='Creates and installs selfsigned certificate for currently set domain')
|
||||
parser_create_selfsigned.set_defaults(action='create-selfsigned')
|
||||
|
||||
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 == 'rebuild-issue':
|
||||
mgr.rebuild_issue()
|
||||
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 == 'update-password':
|
||||
oldpassword = getpass.getpass('Old password: ')
|
||||
newpassword = getpass.getpass('New password: ')
|
||||
mgr.update_password(oldpassword, newpassword)
|
||||
elif args.action == 'create-selfsigned':
|
||||
mgr.create_selfsigned()
|
||||
elif args.action == 'request-cert':
|
||||
mgr.request_cert()
|
||||
elif args.action == 'install-cert':
|
||||
mgr.install_cert(args.certificate, args.key)
|
139
basic/srv/vm/config.json
Normal file
@ -0,0 +1,139 @@
|
||||
{
|
||||
"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": {
|
||||
"adminpwd": "$2b$12$nLrIefUoWN.pK6j90gsfkO0/tg4EGXDmdjN8HOGB0U.9BcHTFxzWS",
|
||||
"domain": "spotter.vm",
|
||||
"port": "443"
|
||||
}
|
||||
}
|
196
basic/srv/vm/static/css/style.css
Normal file
@ -0,0 +1,196 @@
|
||||
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;
|
||||
}
|
||||
|
||||
nav #menu-button {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
nav #menu-button div {
|
||||
width: 24px;
|
||||
height: 4px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #000;
|
||||
margin: 2px 0px;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
display: none;
|
||||
list-style: none;
|
||||
border: 1px solid #000;
|
||||
margin: 26px 0px 0px 0px;
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
padding: 10px;
|
||||
right: 30px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
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;
|
||||
border: solid 1px #000;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.portal-box {
|
||||
position: relative;
|
||||
margin-right: 13px;
|
||||
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="password"],
|
||||
.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); }
|
||||
}
|
BIN
basic/srv/vm/static/img/CAP.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
basic/srv/vm/static/img/CKAN.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
basic/srv/vm/static/img/CTS.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
basic/srv/vm/static/img/Cluster_Spotter.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
basic/srv/vm/static/img/Crisis_Cleanup.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
basic/srv/vm/static/img/Diaspora.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
basic/srv/vm/static/img/EDEN.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
basic/srv/vm/static/img/FrontlineSMS.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
basic/srv/vm/static/img/FrontlineSync.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
basic/srv/vm/static/img/GNU_Health.png
Normal file
After Width: | Height: | Size: 119 KiB |
BIN
basic/srv/vm/static/img/GeoODK_Collect.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
basic/srv/vm/static/img/Kanboard.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
basic/srv/vm/static/img/MifosX.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
basic/srv/vm/static/img/MifosX_Mobile.png
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
basic/srv/vm/static/img/Motech.png
Normal file
After Width: | Height: | Size: 101 KiB |
BIN
basic/srv/vm/static/img/ODK.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
basic/srv/vm/static/img/ODK_Collect.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
basic/srv/vm/static/img/OMK.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
basic/srv/vm/static/img/OpenID.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
basic/srv/vm/static/img/POSM.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
basic/srv/vm/static/img/Pandora.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
basic/srv/vm/static/img/PostGIS.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
basic/srv/vm/static/img/SMS_Sync.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
basic/srv/vm/static/img/SeedDMS.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
basic/srv/vm/static/img/Sigmah.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
basic/srv/vm/static/img/Ushahidi.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
basic/srv/vm/static/img/Ushahidi_mobile.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
basic/srv/vm/static/img/icons/Android.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
basic/srv/vm/static/img/icons/Java.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
basic/srv/vm/static/img/icons/Linux.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
basic/srv/vm/static/img/icons/MacOS.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
basic/srv/vm/static/img/icons/Windows.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
basic/srv/vm/static/img/icons/iOS.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
202
basic/srv/vm/static/js/admin.js
Normal file
@ -0,0 +1,202 @@
|
||||
$(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);
|
||||
$('#update-password').on('submit', update_password);
|
||||
$('#reboot-vm').on('click', reboot_vm);
|
||||
$('#shutdown-vm').on('click', shutdown_vm);
|
||||
});
|
||||
|
||||
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();
|
||||
$('input').prop('disabled', true);
|
||||
$('.setup-box').slice(1).css('opacity', '0.5');
|
||||
}
|
||||
});
|
||||
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').html(data.error).show();
|
||||
$('#cert-submit').show();
|
||||
} else {
|
||||
$('#cert-message').attr('class','info').html(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;
|
||||
}
|
||||
|
||||
function update_password() {
|
||||
$('#password-submit').hide();
|
||||
$('#password-message').hide();
|
||||
$('#password-wait').show();
|
||||
$.post('/update-password', {'oldpassword': $('#oldpassword').val(), 'newpassword': $('#newpassword').val(), 'newpassword2': $('#newpassword2').val()}, function(data) {
|
||||
$('#password-wait').hide();
|
||||
if (data.error) {
|
||||
$('#password-message').attr('class','error').html(data.error).show();
|
||||
$('#password-submit').show();
|
||||
} else {
|
||||
$('#password-message').attr('class','info').html(data.ok).show();
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function reboot_vm() {
|
||||
if (confirm('Do you really want to reboot VM?')) {
|
||||
$.get('/reboot-vm', function(data) {
|
||||
$('#vm-message').attr('class','info').html(data.ok).show();
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function shutdown_vm() {
|
||||
if (confirm('Do you really want to shutdown VM?')) {
|
||||
$.get('/shutdown-vm', function(data) {
|
||||
$('#vm-message').attr('class','info').html(data.ok).show();
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
2
basic/srv/vm/static/js/jquery-3.3.1.min.js
vendored
Normal file
7
basic/srv/vm/static/js/script.js
Normal file
@ -0,0 +1,7 @@
|
||||
$(function() {
|
||||
$('#menu-button').on('click', toggle_menu);
|
||||
});
|
||||
|
||||
function toggle_menu() {
|
||||
$('#menu').toggle();
|
||||
}
|
15
basic/srv/vm/templates/404.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!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>Chyba 404</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Stránka nebyla nalezena</h1>
|
||||
<p>Stránka, kterou se pokoušíte zobrazit, nebyla na serveru nalezena. Zkontrolujte prosím URL v adresním řádku nebo se vraťte <a href="/">zpět na úvodní stránku</a>.</p>
|
||||
</body>
|
||||
</html>
|
15
basic/srv/vm/templates/502.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!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>Chyba 502</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Chyba spojení s aplikací</h1>
|
||||
<p>Aplikace ke které se pokoušíte připojit není dostupná. Nejspíše byla vypnuta správcem serveru.</p>
|
||||
</body>
|
||||
</html>
|
42
basic/srv/vm/templates/layout.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!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>
|
||||
{% if session.admin %}
|
||||
<script src="static/js/admin.js"></script>
|
||||
{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<div id="menu-button">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<ul id="menu">
|
||||
<li><a href="/">Portál</a></li>
|
||||
{% if session.admin %}
|
||||
<li><a href="/setup-host">Nastavení hostitele</a></li>
|
||||
<li><a href="/setup-apps">Nastavení aplikací</a></li>
|
||||
<li><a href="/logout">Odhlášení</a></li>
|
||||
{% else %}
|
||||
<li><a href="/login">Přihlášení</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
<header>
|
||||
<h1>CLUSTER NGO</h1>
|
||||
<p>Sada softwarových nástrojů určená pro krizový management.</p>
|
||||
</header>
|
||||
{% block body %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
28
basic/srv/vm/templates/login.html
Normal file
@ -0,0 +1,28 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% block title %}Přihlášení{% endblock %}
|
||||
{% block body %}
|
||||
<div class="setup-box">
|
||||
<h2>Přihlášení</h2>
|
||||
<form action="/login" method="post">
|
||||
<table>
|
||||
<tr>
|
||||
<td>Jméno:</td>
|
||||
<td>admin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Heslo</td>
|
||||
<td><input type="password" name="password"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<input type="submit" value="Přihlásit">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% if message is defined %}
|
||||
<p class="error">{{ message }}</p>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
352
basic/srv/vm/templates/portal-admin.html
Normal file
@ -0,0 +1,352 @@
|
||||
{% 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'] and is_service_started('sahana') %}
|
||||
<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'] and is_service_started('sahana-demo') %}
|
||||
<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'] and is_service_started('sambro') %}
|
||||
<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'] and is_service_started('crisiscleanup') %}
|
||||
<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'] and is_service_started('ckan') %}
|
||||
<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'] and is_service_started('opendatakit-build') %}
|
||||
<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="https://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'] and is_service_started('opendatakit') %}
|
||||
<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'] and is_service_started('openmapkit') %}
|
||||
<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'] and is_service_started('frontlinesms') %}
|
||||
<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/FrontlineSync.png" alt="FrontlineSync" title="FrontlineSync">FrontlineSync</a></h2>
|
||||
<p>Mobilní aplikace pro<br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.simlab.frontlinesync"><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'] and is_service_started('seeddms') %}
|
||||
<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'] and is_service_started('pandora') %}
|
||||
<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'] and is_service_started('ushahidi') %}
|
||||
<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>
|
||||
|
||||
<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']['kanboard'] %}
|
||||
{% if app['visible'] and is_service_started('kanboard') %}
|
||||
<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'] and is_service_started('cts') %}
|
||||
<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'] and is_service_started('gnuhealth') %}
|
||||
<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'] and is_service_started('sigmah') %}
|
||||
<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'] and is_service_started('motech') %}
|
||||
<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'] and is_service_started('mifosx') %}
|
||||
<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="https://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 %}
|
123
basic/srv/vm/templates/portal-user.html
Normal file
@ -0,0 +1,123 @@
|
||||
{% 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'] %}
|
||||
{% if conf['apps']['sahana-demo']['visible'] and is_service_started('sahana-demo') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://sahana-demo.{{ host }}/eden/">Řízení humanítární činnosti</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>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['sambro']['visible'] and is_service_started('sambro') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://sambro.{{ host }}/eden/">Centrum hlášení a výstrah</a></h2>
|
||||
<p>Samostatná instance s šablonou pro centrum hlášení a výstrah.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['crisiscleanup']['visible'] and is_service_started('crisiscleanup') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://cc.{{ host }}">Mapování následků katastrof</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>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['ckan']['visible'] and is_service_started('ckan') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://ckan.{{ host }}">Datový sklad</a></h2>
|
||||
<p><strong>Repository</strong> management a datová analýza pro vytváření otevřených dat.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['opendatakit-build']['visible'] and is_service_started('opendatakit-build') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://odkbuild.{{ host }}">Sběr formulářových dat</a></h2>
|
||||
<p><strong>Sběr dat s pomocí smartphone</strong>.<br>Aplikace pro návrh formulářů</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['opendatakit']['visible'] and is_service_started('opendatakit') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://odk.{{ host }}/">Sběr formulářových dat</a></h2>
|
||||
<p><strong>Sběr dat s pomocí smartphone</strong>.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['openmapkit']['visible'] and is_service_started('openmapkit') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://omk.{{ host }}">Sběr mapových dat</a></h2>
|
||||
<p><strong>Sběr dat s pomocí smartphone</strong>.<br>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['frontlinesms']['visible'] and is_service_started('frontlinesms') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://sms.{{ host }}">Hromadné odesílání zpráv</a></h2>
|
||||
<p><strong>SMS messaging</strong> přes veřejné datové brány</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['seeddms']['visible'] and is_service_started('seeddms') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://dms.{{ host }}">Archiv dokumentace</a></h2>
|
||||
<p><strong>Dokument management</strong> na dokumentaci a projektovou dokumentaci</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['pandora']['visible'] and is_service_started('pandora') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://pandora.{{ host }}">Archiv medií</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>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['ushahidi']['visible'] and is_service_started('ushahidi') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://ush.{{ host }}">Skupinová reakce na události</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>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['kanboard']['visible'] and is_service_started('kanboard') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://kb.{{ host }}">Kanban řízení projektů</a></h2>
|
||||
<p>Usnadňuje tvorbu a řízení projektů s pomocí Kanban metodiky.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['gnuhealth']['visible'] and is_service_started('gnuhealth') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://gh.{{ host }}/index.html">Lékařské záznamy pacientů</a></h2>
|
||||
<p>Zdravotní a nemocniční informační systém.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['sigmah']['visible'] and is_service_started('sigmah') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://sigmah.{{ host }}/sigmah/">Finanční řízení sbírek</a></h2>
|
||||
<p>Rozpočtování získávání finančních prostředků.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['motech']['visible'] and is_service_started('motech') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://motech.{{ host }}/">Automatizace komunikace</a></h2>
|
||||
<p>Integrace zdravotnických a komunikačních služeb.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if conf['apps']['mifosx']['visible'] and is_service_started('mifosx') %}
|
||||
<div class="portal-box">
|
||||
<h2><a href="https://mifosx.{{ host }}/">Mikrofinancování rozvojových projektů</a></h2>
|
||||
<p>Nástroj na rozvojovou, humanitární pomoc a mikrofinancování.</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 %}
|
94
basic/srv/vm/templates/setup-apps.html
Normal file
@ -0,0 +1,94 @@
|
||||
{% 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>
|
||||
|
||||
<div class="setup-box">
|
||||
<h2>Správce virtuálního stroje</h2>
|
||||
<p>Změna hesla k šifrovanému diskovému oddílu a administračnímu rozhraní.</p>
|
||||
<form id="update-password" action="/update-password" method="post">
|
||||
<table>
|
||||
<tr>
|
||||
<td>Stávající heslo:</td>
|
||||
<td><input type="password" name="oldpassword" id="oldpassword"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nové heslo:</td>
|
||||
<td><input type="password" name="newpassword" id="newpassword"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kontrola nového hesla:</td>
|
||||
<td><input type="password" name="newpassword2" id="newpassword2"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td colspan="2">
|
||||
<input type="submit" id="password-submit" value="Změnit heslo">
|
||||
<div id="password-message"></div>
|
||||
<div id="password-wait" class="loader-wrap">
|
||||
<div class="loader"></div>
|
||||
<span>Provádí se změna hesla, prosím čekejte...</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<p>Restartování nebo vypnutí virtuálního stroje.</p>
|
||||
<input type="button" id="reboot-vm" value="Restartovat VM">
|
||||
<input type="button" id="shutdown-vm" value="Vypnout VM">
|
||||
<div id="vm-message"></div>
|
||||
</div>
|
||||
{% endblock %}
|
115
basic/srv/vm/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 %}
|
16
basic/srv/vm/wsgi.py
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
|
||||
sys.path.append('/srv/vm')
|
||||
from appmgr.wsgiapp import WSGIApp
|
||||
|
||||
application = WSGIApp()
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
from werkzeug.contrib.fixers import ProxyFix
|
||||
from werkzeug.serving import run_simple
|
||||
|
||||
run_simple('127.0.0.1', 8080, ProxyFix(application))
|
@ -1,117 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -ev
|
||||
|
||||
ROOT=$(dirname $(dirname $(realpath "${0}")))
|
||||
|
||||
# Build documentation
|
||||
cd ${ROOT}/doc
|
||||
make html
|
||||
|
||||
# Build basic tar
|
||||
cd ${ROOT}/vm
|
||||
tar czpf /srv/build/vm.tar.gz *
|
||||
|
||||
# Build native apps
|
||||
cd ${ROOT}/apk/py3-secure-cookie
|
||||
apk add -U py3-setuptools py3-pytest py3-werkzeug
|
||||
abuild -F
|
||||
|
||||
cd ${ROOT}/apk/spoc
|
||||
abuild -F
|
||||
|
||||
cd ${ROOT}/apk/vmmgr
|
||||
abuild -F
|
||||
|
||||
# Build runtimes
|
||||
cd ${ROOT}/lxc-shared
|
||||
spoc-image build -p alpine3.8/image
|
||||
spoc-image build -p alpine3.8-java8/image
|
||||
spoc-image build -p alpine3.8-ruby2.4/image
|
||||
spoc-image build -p alpine3.10/image
|
||||
spoc-image build -p alpine3.10-nodejs10/image
|
||||
spoc-image build -p alpine3.11/image
|
||||
spoc-image build -p alpine3.11-python2.7/image
|
||||
spoc-image build -p alpine3.12/image
|
||||
spoc-image build -p alpine3.12-java8/image
|
||||
spoc-image build -p alpine3.12-php7.3/image
|
||||
spoc-image build -p alpine3.12-python3.8/image
|
||||
spoc-image build -p alpine3.12-ruby2.4/image
|
||||
spoc-image build -p alpine3.12-ruby2.7/image
|
||||
spoc-image build -p alpine3.12-tomcat7/image
|
||||
spoc-image build -p alpine3.12-tomcat8.5/image
|
||||
spoc-image build -p debian10/image
|
||||
|
||||
# Build services
|
||||
cd ${ROOT}/lxc-services
|
||||
spoc-image build -p activemq/image
|
||||
spoc-image build -p mariadb/image
|
||||
spoc-image build -p mongodb/image
|
||||
spoc-image build -p postgres/image
|
||||
spoc-image build -p postgis/image
|
||||
spoc-image build -p rabbitmq/image
|
||||
spoc-image build -p redis/image
|
||||
spoc-image build -p solr6/image
|
||||
|
||||
# Build applications
|
||||
cd ${ROOT}/lxc-apps
|
||||
|
||||
spoc-image build -p ckan/ckan.image
|
||||
spoc-image build -p ckan/ckan-datapusher.image
|
||||
spoc-app publish ckan/app
|
||||
|
||||
spoc-image build -p crisiscleanup/image
|
||||
spoc-app publish crisiscleanup/app
|
||||
|
||||
spoc-image build -p cts/image
|
||||
spoc-app publish cts/app
|
||||
|
||||
spoc-image build -p decidim/decidim-nginx.image
|
||||
spoc-image build -p decidim/decidim.image
|
||||
spoc-app publish decidim/app
|
||||
|
||||
spoc-image build -p dhis2/image
|
||||
spoc-app publish dhis2/app
|
||||
|
||||
spoc-image build -p frontlinesms/image
|
||||
spoc-app publish frontlinesms/app
|
||||
|
||||
spoc-image build -p gnuhealth/image
|
||||
spoc-app publish gnuhealth/app
|
||||
|
||||
spoc-image build -p kanboard/image
|
||||
spoc-app publish kanboard/app
|
||||
|
||||
spoc-image build -p mifosx/image
|
||||
spoc-app publish mifosx/app
|
||||
|
||||
spoc-image build -p motech/image
|
||||
spoc-app publish motech/app
|
||||
|
||||
spoc-image build -p odoo/image
|
||||
spoc-app publish odoo/app
|
||||
|
||||
spoc-image build -p opendatakit/opendatakit.image
|
||||
spoc-image build -p opendatakit/opendatakit-build.image
|
||||
spoc-app publish opendatakit/app
|
||||
|
||||
spoc-image build -p openmapkit/image
|
||||
spoc-app publish openmapkit/app
|
||||
|
||||
spoc-image build -p pandora/image
|
||||
spoc-app publish pandora/app
|
||||
|
||||
spoc-image build -p sahana/image
|
||||
spoc-app publish sahana/app
|
||||
spoc-app publish sahana-demo/app
|
||||
spoc-app publish sambro/app
|
||||
spoc-app publish safire/app
|
||||
spoc-app publish share/app
|
||||
|
||||
spoc-image build -p seeddms/image
|
||||
spoc-app publish seeddms/app
|
||||
|
||||
spoc-image build -p taarifa/image
|
||||
spoc-app publish taarifa/app
|
||||
|
||||
spoc-image build -p ushahidi/image
|
||||
spoc-app publish ushahidi/app
|
@ -1,29 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -ev
|
||||
|
||||
# Clean documentation
|
||||
rm -rf /srv/build/doc/*
|
||||
|
||||
# Clean basic tar
|
||||
rm -f /srv/build/vm.tar.gz
|
||||
|
||||
# Clean native apps
|
||||
rm -rf /srv/build/alpine/*
|
||||
|
||||
# Clean built LXC packages
|
||||
rm -rf /srv/build/spoc
|
||||
|
||||
# Remove nginx configs
|
||||
for CONF in $(find /etc/nginx/conf.d -name '*.conf' -a ! -name repo.conf -a ! -name default.conf); do
|
||||
rm -f ${CONF}
|
||||
done
|
||||
service nginx reload
|
||||
|
||||
# Stop running containers
|
||||
for APP in $(spoc-container list); do
|
||||
spoc-container stop ${APP}
|
||||
done
|
||||
|
||||
# Remove data
|
||||
rm -rf /var/lib/spoc
|
||||
rm -rf /var/log/spoc
|
@ -1,32 +0,0 @@
|
||||
export CFLAGS="-Os -fomit-frame-pointer"
|
||||
export CXXFLAGS="$CFLAGS"
|
||||
export CPPFLAGS="$CFLAGS"
|
||||
export LDFLAGS="-Wl,--as-needed"
|
||||
|
||||
export JOBS=2
|
||||
export MAKEFLAGS=-j$JOBS
|
||||
|
||||
# remove line below to disable colors
|
||||
USE_COLORS=1
|
||||
|
||||
# uncomment line below to enable ccache support.
|
||||
#USE_CCACHE=1
|
||||
|
||||
SRCDEST=/var/cache/distfiles
|
||||
|
||||
# uncomment line below to store built packages in other location
|
||||
# The package will be stored as $REPODEST/$repo/$pkgname-$pkgver-r$pkgrel.apk
|
||||
# where $repo is the name of the parent directory of $startdir.
|
||||
REPODEST=/srv/build/alpine/v3.12
|
||||
|
||||
# PACKAGER and MAINTAINER are used by newapkbuild when creating new aports for
|
||||
# the APKBUILD's "Contributor:" and "Maintainer:" comments, respectively.
|
||||
#PACKAGER="Your Name <your@email.address>"
|
||||
#MAINTAINER="$PACKAGER"
|
||||
PACKAGER_PRIVKEY="/root/repo.spotter.cz.rsa"
|
||||
|
||||
# what to clean up after a successful build
|
||||
CLEANUP="srcdir bldroot pkgdir deps"
|
||||
|
||||
# what to cleanup after a failed build
|
||||
ERROR_CLEANUP="bldroot deps"
|
@ -1,8 +0,0 @@
|
||||
server {
|
||||
listen [::]:80;
|
||||
server_name repo.build.vm;
|
||||
|
||||
location / {
|
||||
root /srv/build;
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -ev
|
||||
|
||||
cd $(realpath $(dirname "${0}"))
|
||||
|
||||
# Install basic build tools
|
||||
apk update
|
||||
apk add git file htop less openssh-client tree
|
||||
# Install Alpine SDK (for APK builds)
|
||||
apk add alpine-sdk
|
||||
# Install Sphinx support (for documentation builds)
|
||||
apk add py3-sphinx py3-sphinx_rtd_theme
|
||||
|
||||
# Copy root profile files and settings
|
||||
mkdir -p /root/.config/htop
|
||||
cp root/.profile /root/.profile
|
||||
cp root/.config/htop/htoprc /root/.config/htop/htoprc
|
||||
|
||||
# Prepare abuild toolchain
|
||||
adduser root abuild
|
||||
cp etc/abuild.conf /etc/abuild.conf
|
||||
|
||||
# Prepare local APK repository
|
||||
cp etc/nginx/conf.d/repo.conf /etc/nginx/conf.d/repo.conf
|
||||
echo "172.17.0.1 repo.build.vm" >>/etc/hosts
|
||||
service nginx reload
|
||||
|
||||
# Change SPOC repository
|
||||
sed -i 's/https:\/\/repo\.spotter\.cz/http:\/\/repo.build.vm/' /etc/spoc/spoc.conf
|
||||
|
||||
# Supply abuild key
|
||||
# echo '/root/repo.spotter.cz.rsa' | abuild-keygen
|
||||
|
||||
# Supply SPOC key
|
||||
# openssl ecparam -genkey -name secp384r1 -out /etc/spoc/publish.key
|
||||
# openssl ec -in /etc/spoc/publish.key -pubout -out /tmp/repository.pub
|
@ -1,2 +0,0 @@
|
||||
alias ll="ls -la"
|
||||
alias view="vi -R"
|