Move everything to docker containers

This commit is contained in:
Kyle Gabriel 2019-03-03 00:24:24 -05:00
parent 70b15eda8a
commit 291fcdb992
51 changed files with 239 additions and 86 deletions

6
Makefile Normal file
View file

@ -0,0 +1,6 @@
build:
docker-compose up --build -d
clean:
docker-compose down
docker system prune -fa

View file

@ -1,8 +1,10 @@
## The Things Network Tracker (TTN-Tracker)
This is a [Flask](http://flask.pocoo.org/) app served via [Gunicorn](https://github.com/benoitc/gunicorn) and [Nginx](http://nginx.org/) using [docker](https://www.docker.com/) containers orchestrated by docker-compose.
For use with [kizniche/ttgo-tbeam-ttnmapper](https://github.com/kizniche/ttgo-tbeam-ttnmapper) for the T-Beam TTGO (tracker node) that transmits data to [The Things Network](https://thethingsnetwork.org) (TTN) for [TTN Mapper](https://ttnmapper.org/).
This Flask app hosts a web-enabled front end using [gunicorn](https://github.com/benoitc/gunicorn) and [nginx](http://nginx.org/). It pulls coordinate data acquired from the tracker node that's been stored on TTN. It stores these coordinates in an SQLite database and displays the coordinates on a map ([leaflet](https://github.com/Leaflet/Leaflet)) in your web browser. This is useful for testing the signal range from gateways while driving, so you can see when and where your signal was able to reach a gateway.
This app pulls coordinate data acquired from the tracker node that's been stored on TTN. It stores these coordinates in an SQLite database and displays the coordinates on a map ([leaflet](https://github.com/Leaflet/Leaflet)) in your web browser. This is useful for testing the signal range from gateways while driving, so you can see when and where your signal was able to reach a gateway.
This is very similar to the TTN Mapper frontend, however TTN Mapper takes a long time to update the data points on its map. This software runs locally on your own hardware and responds instantly to new data on TTN, making it a good companion in your vehicle if you want to get instant updates as to whether your tracker node successfully communicated its coordinates or not.
@ -15,28 +17,71 @@ Features include:
### Setup:
I've succesfully set this up on a Raspberry Pi, but these instructions should work for any debian-variant operating system. Other systems you may have to adapt how you install the prerequisites.
I've successfully set this up on a Raspberry Pi.
Make sure you have your application set up on The Things Network with the integration "Data Storage". The integration "TTN Mapper" is optional but is recommended to be able to provide signal data to the public.
### Install docker and docker-compose
```
curl -sSL https://get.docker.com | sh
sudo pip install docker-compose
```
### Clone this repository
```
git clone https://github.com/kizniche/ttn-tracker.git
cd ttn-tracker
sudo pip install virtualenv --upgrade
PYTHON_BINARY_SYS_LOC="$(python3.5 -c "import os; print(os.environ['_'])")"
virtualenv --system-site-packages -p ${PYTHON_BINARY_SYS_LOC} ./env
./env/bin/pip install -r requirements.txt
sudo ln -s /home/pi/ttn-tracker/flask_nginx.conf /etc/nginx/sites-enabled/ttn-tracker_nginx.config
sudo service nginx restart
sudo systemctl enable /home/pi/ttn-tracker/ttn-tracker.service
```
Make sure you have your application set up on The Things Network with the integration "Data Storage". Edit config.py with your application API Key, application ID, Device ID(s), and gateway location(s). The integration "TTN Mapper" is optional but is recommended to be able to provide signal data to the public.
### Edit the config file
### Run
Edit ttn-tracker/flask_app/config.py with your application API Key, application ID, Device ID(s), and gateway location(s) before building the docker image. If you need to edit this file after the image is created, you can rebuild the image (destroying data) or copy the new config file over the old while the flask_app container is running (see below).
```sudo service ttn-tracker start```
### Build and start the app
This will build and start the app and keep it running across reboots.
```sudo make build```
### Stop app (preserving data)
```sudo docker-compose stop```
### Start app after stopping
```sudo docker-compose start```
### Web Address
Note: there is no security preventing someone from viewing this page if they happen to request "/dsf673bh" on the server (however, knowing this is the page is unlikely). Therefore, make sure you are comfortable with this or implement your own security measures such as not allowing port 5500 to be publicly accessible (connect to your home network via VPN to access the app) or add a login system such as [Flask-Login](https://github.com/maxcountryman/flask-login).
Open a browser to this address, replacing IP_ADDRESS with the IP address of the system running the docker containers.
http://127.0.0.1:5500/dsf673bh
http://IP_ADDRESS:5550/dsf673bh
Note: there is no security preventing someone from viewing this page if they happen to request "/dsf673bh" on the server (however, knowing this is the page is unlikely). Therefore, make sure you are comfortable with this or implement your own security measures such as not allowing port 5550 to be publicly accessible (connect to your home network via VPN to access the app) or add a login system such as [Flask-Login](https://github.com/maxcountryman/flask-login).
### Notes
#### Stop app and delete data (keep containers)
```sudo docker-compose down```
#### Stop app and delete containers
```sudo docker-compose rm -fs```
#### List docker containers
```sudo docker ps```
#### Start a shell in a docker container
```sudo docker exec -i -t CONTAINER_ID /bin/bash```
#### Copy files to/from a docker container
```
docker cp foo.txt CONTAINER_ID:/foo.txt
docker cp CONTAINER_ID:/foo.txt foo.txt
```

View file

View file

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
# Where the map initially loads
start_lat = 35.978781
start_lon = -77.855346
# Where to store SQLite database
path_db = '/home/pi/ttn-tracker/ttnmapper_retrieve.db'
# TTN Application
application = "ttn_application"
app_key = "key ttn-account-TTN_APP_KEY"
# Application devices
devices = [
"device_01",
"device_02"
]
# Where to place gateway markers
gateway_locations = [
('Gateway 01', 35.978781, -77.855346),
('Gateway 02', 35.978781, -77.655346)
]

21
docker-compose.yml Normal file
View file

@ -0,0 +1,21 @@
version: "3"
services:
flask_app:
container_name: flask_app
restart: always
build: ./flask_app
ports:
- "8000:8000"
command: gunicorn -w 1 --worker-class gthread --bind :8000 app:app
nginx:
container_name: nginx
restart: always
build: ./nginx
ports:
- "5550:5550"
depends_on:
- flask_app

8
flask_app/Dockerfile Normal file
View file

@ -0,0 +1,8 @@
FROM python:3.6-slim-stretch
RUN mkdir -pv /home/project/flask_app
WORKDIR /home/project/flask_app
COPY requirements.txt /home/project/flask_app
RUN pip install --no-cache-dir -r requirements.txt
COPY . /home/project/flask_app

View file

@ -6,27 +6,26 @@ from math import radians
from math import sin
from math import sqrt
import os
import requests
from apscheduler.schedulers.background import BackgroundScheduler
from flask import Flask
from flask import render_template
from flask_sqlalchemy import SQLAlchemy
from config import app_key
from config import application
from config import bing_api_key
from config import config_app
from config import devices
from config import gateway_locations
from config import path_db
from config import start_lat
from config import start_lon
from flask import Flask
from flask import render_template
from flask_sqlalchemy import SQLAlchemy
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
logger = logging.getLogger(__name__)
app = Flask(__name__, template_folder="./templates")
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///{db}'.format(db=path_db)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app = config_app(Flask(__name__, template_folder="./templates"))
db = SQLAlchemy(app)
@ -66,10 +65,15 @@ class LastAcquisition(db.Model):
return '<ID %r>' % self.id
if not os.path.exists(path_db):
db.create_all()
@app.route('/dsf673bh')
def hello_world():
def main_page():
get_new_data()
return render_template('map.html',
bing_api_key=bing_api_key,
gateway_locations=gateway_locations,
location_data=Location.query.all(),
start_lat=start_lat,
@ -147,4 +151,4 @@ def distance_coordinates(lat1, lon1, lat2, lon2):
if __name__ == '__main__':
db.create_all()
app.run(debug=True, host='0.0.0.0', port=5500)
app.run(debug=True, host='0.0.0.0', port=8000)

51
flask_app/config.py Normal file
View file

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Where to store SQLite database
path_db = '/home/project/ttn_tracker_database.db'
# Where the map initially loads
start_lat = 35.978781
start_lon = -77.855346
# TTN Application
application = "ttn_application"
app_key = "key ttn-account-TTN_APP_KEY"
# Application devices
devices = [
"device_01",
"device_02"
]
# Where to place gateway markers
gateway_locations = [
('Gateway 01', 35.978781, -77.855346),
('Gateway 02', 35.978781, -77.655346)
]
bing_api_key = ''
def config_app(app, **kwargs):
"""Dash app configuration
Parameters
----------
app: Dash app
debug: optional, default=False
Returns
-------
app: Dash app
With added css, ga and layout container with:
app-layout: main div, should not be a target of an ouput callback
page-content: container div, target for an ouput callback
url: Location, target of an input callback
"""
if kwargs.get('debug', False):
app.server.debug = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///{db}'.format(db=path_db)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
return app

View file

Before

(image error) Size: 397 B

After

(image error) Size: 397 B

View file

Before

(image error) Size: 588 B

After

(image error) Size: 588 B

View file

Before

(image error) Size: 762 B

After

(image error) Size: 762 B

View file

Before

(image error) Size: 387 B

After

(image error) Size: 387 B

View file

Before

(image error) Size: 522 B

After

(image error) Size: 522 B

View file

Before

(image error) Size: 692 B

After

(image error) Size: 692 B

View file

Before

(image error) Size: 326 B

After

(image error) Size: 326 B

View file

Before

(image error) Size: 607 B

After

(image error) Size: 607 B

View file

Before

(image error) Size: 462 B

After

(image error) Size: 462 B

View file

Before

(image error) Size: 192 B

After

(image error) Size: 192 B

View file

Before

(image error) Size: 2.1 KiB

After

(image error) Size: 2.1 KiB

View file

Before

(image error) Size: 277 B

After

(image error) Size: 277 B

View file

Before

(image error) Size: 491 B

After

(image error) Size: 491 B

View file

Before

(image error) Size: 628 B

After

(image error) Size: 628 B

View file

Before

(image error) Size: 1,003 B

After

(image error) Size: 1,003 B

View file

Before

(image error) Size: 279 B

After

(image error) Size: 279 B

View file

Before

(image error) Size: 2.5 KiB

After

(image error) Size: 2.5 KiB

View file

Before

(image error) Size: 460 B

After

(image error) Size: 460 B

View file

Before

(image error) Size: 1.2 KiB

After

(image error) Size: 1.2 KiB

View file

Before

(image error) Size: 696 B

After

(image error) Size: 696 B

View file

Before

(image error) Size: 2.4 KiB

After

(image error) Size: 2.4 KiB

View file

Before

(image error) Size: 1.4 KiB

After

(image error) Size: 1.4 KiB

View file

Before

(image error) Size: 618 B

After

(image error) Size: 618 B

View file

@ -35,8 +35,8 @@
esri_Map = L.esri.basemapLayer("Topographic"),
otm_Map = L.tileLayer(otm_Url, {attribution: otm_Attrib}),
google_Map = L.tileLayer(google_Url, {attribution: google_Attrib}),
bing_dark = L.tileLayer.bing({bingMapsKey: 'Ahpy5iReID6QJSUjazLaJbAkUMg2R990DMsYlbxMbf3irXoOVgFb0eyV3JPntW2Q', imagerySet: 'CanvasDark', attribution: bing_Attrib}),
bing_sat = L.tileLayer.bing({bingMapsKey: 'Ahpy5iReID6QJSUjazLaJbAkUMg2R990DMsYlbxMbf3irXoOVgFb0eyV3JPntW2Q', imagerySet: 'Aerial', attribution: bing_Attrib}),
bing_dark = L.tileLayer.bing({bingMapsKey: '{{bing_api_key}}', imagerySet: 'CanvasDark', attribution: bing_Attrib}),
bing_sat = L.tileLayer.bing({bingMapsKey: '{{bing_api_key}}', imagerySet: 'Aerial', attribution: bing_Attrib}),
bing_sat_labels = L.tileLayer.bing({bingMapsKey: 'Ahpy5iReID6QJSUjazLaJbAkUMg2R990DMsYlbxMbf3irXoOVgFb0eyV3JPntW2Q', imagerySet: 'AerialWithLabels', attribution: bing_Attrib});
//Create a map that remembers where it was zoomed to

View file

@ -1,15 +0,0 @@
server {
listen 5500;
client_max_body_size 30M;
location / {
include proxy_params;
proxy_pass http://unix:/var/run/ttn_tracker_flask.sock;
}
error_page 502 /502.html;
location = /502.html {
root /home/pi/ttn-tracker/templates;
}
}

7
gitignore Normal file
View file

@ -0,0 +1,7 @@
__pycache__
.vscode/
.idea/
.cache/
.DS_Store

7
nginx/Dockerfile Normal file
View file

@ -0,0 +1,7 @@
FROM nginx:1.15.9
RUN rm /etc/nginx/nginx.conf
COPY nginx.conf /etc/nginx/
RUN rm /etc/nginx/conf.d/default.conf
COPY project.conf /etc/nginx/conf.d/

52
nginx/nginx.conf Normal file
View file

@ -0,0 +1,52 @@
# Define the user that will own and run the Nginx server
user nginx;
# Define the number of worker processes; recommended value is the number of
# cores that are being used by your server
worker_processes 1;
# Define the location on the file system of the error log, plus the minimum
# severity to log messages for
error_log /var/log/nginx/error.log warn;
# Define the file that will store the process ID of the main NGINX process
pid /var/run/nginx.pid;
# events block defines the parameters that affect connection processing.
events {
# Define the maximum number of simultaneous connections that can be opened by a worker process
worker_connections 1024;
}
# http block defines the parameters for how NGINX should handle HTTP web traffic
http {
# Include the file defining the list of file types that are supported by NGINX
include /etc/nginx/mime.types;
# Define the default file type that is returned to the user
default_type text/html;
# Define the format of log messages.
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Define the location of the log of access attempts to NGINX
access_log /var/log/nginx/access.log main;
# Define the parameters to optimize the delivery of static content
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Define the timeout value for keep-alive connections with the client
keepalive_timeout 65;
# Define the usage of the gzip compression algorithm to reduce the amount of data to transmit
#gzip on;
# Include additional parameters for virtual host(s)/server(s)
include /etc/nginx/conf.d/*.conf;
}

12
nginx/project.conf Normal file
View file

@ -0,0 +1,12 @@
server {
listen 5550;
server_name ttn_tracker;
location / {
proxy_pass http://flask_app:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

View file

@ -1,21 +0,0 @@
[Unit]
Description=gunicorn daemon for ttn-mapper
After=network.target
[Service]
User=root
Group=pi
WorkingDirectory=/home/pi/ttn-tracker
ExecStart=/home/pi/ttn-tracker/env/bin/gunicorn \
--workers 1 \
--worker-class gthread \
--threads 2 \
--timeout 300 \
--pid /var/lock/ttn_tracker_flask.pid \
--bind unix:/var/run/ttn_tracker_flask.sock app:app
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
[Install]
WantedBy=multi-user.target