From 6f25cb4691b5a64c86ddeca670cf9c2cb7074108 Mon Sep 17 00:00:00 2001 From: mdivecky Date: Thu, 19 Oct 2023 18:37:19 +0200 Subject: [PATCH] init --- .gitignore | 3 ++ README.md | 16 ++++++++ docker-compose.yml | 31 +++++++++++++++ env.sample | 1 + flask/Dockerfile | 32 +++++++++++++++ flask/app.py | 79 ++++++++++++++++++++++++++++++++++++++ flask/requirements.txt | 3 ++ flask/templates/index.html | 55 ++++++++++++++++++++++++++ flask/wsgi.py | 5 +++ nginx/Dockerfile | 32 +++++++++++++++ nginx/default.conf | 18 +++++++++ nginx/nginx.conf | 50 ++++++++++++++++++++++++ nginx/start.sh | 2 + 13 files changed, 327 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 env.sample create mode 100644 flask/Dockerfile create mode 100644 flask/app.py create mode 100644 flask/requirements.txt create mode 100644 flask/templates/index.html create mode 100644 flask/wsgi.py create mode 100644 nginx/Dockerfile create mode 100644 nginx/default.conf create mode 100644 nginx/nginx.conf create mode 100644 nginx/start.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce2ccaa --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*/__pycache__ +.env +*.pyc \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d51543 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ + +Aktuálně jsou hardcodované dvě zastávky v proměnné na začátku kódu, dalo by se hodit do proměné třeba jako json. +Ryšánka - směr Budějovická (C) - U632Z3P +Na Vrstevnici - směr Přístaviště (A) - U436Z1P + +Název zastávky a její stanoviště (A,B,C,D...) je možné najít na [webu PID](https://pid.cz/zastavkova-tabla/?stop=Na+Vrstevnici&stanoviste=&tab=3) +Název je pak možné hodit do [Swaggeru od Golemia](https://api.golemio.cz/pid/docs/openapi/#/%F0%9F%A7%BE%20GTFS%20Static/get_gtfs_stops) (get_gtfs_stops) a ve výstupu najít odpovídající ID zastávky (`stop_id`) + +Před prvním spuštěním je potřeba dostat API key od Golemia, jde to zdarma [zde](https://api.golemio.cz/api-keys/auth/sign-in). Ten pak vložit do souboru .env jako proměnnou `API_KEY`. + +"Dockerizováno" primárně podle návodu [tady](https://github.com/docker/awesome-compose/blob/master/nginx-wsgi-flask/compose.yaml) + +## Todo +* Auto refresh? +* Cache pro data z Golemia? +* Přesun IDs zastávek do proměnné \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bea5316 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,31 @@ +version: "3.3" + +services: + nginx-proxy: + build: nginx + restart: always + volumes: + - ./nginx/default.conf:/tmp/default.conf + environment: + - FLASK_SERVER_ADDR=flask-app:8000 + ports: + - "127.0.0.1:7880:80" + depends_on: + - flask-app + healthcheck: + test: ["CMD-SHELL", "curl --silent --fail localhost:80/health-check || exit 1"] + interval: 10s + timeout: 10s + retries: 3 + command: /app/start.sh + flask-app: + build: flask + restart: always + environment: + - API_KEY=${API_KEY} + healthcheck: + test: ["CMD-SHELL", "curl --silent --fail localhost:8000/flask-health-check || exit 1"] + interval: 10s + timeout: 10s + retries: 3 + command: gunicorn -w 3 -t 60 -b 0.0.0.0:8000 app:app diff --git a/env.sample b/env.sample new file mode 100644 index 0000000..2387a6f --- /dev/null +++ b/env.sample @@ -0,0 +1 @@ +API_KEY= \ No newline at end of file diff --git a/flask/Dockerfile b/flask/Dockerfile new file mode 100644 index 0000000..e21b756 --- /dev/null +++ b/flask/Dockerfile @@ -0,0 +1,32 @@ +FROM python:alpine + +# upgrade pip +RUN pip install --upgrade pip + +# get curl for healthchecks +RUN apk add curl + +# permissions and nonroot user for tightened security +RUN adduser -D nonroot +RUN mkdir /home/app/ && chown -R nonroot:nonroot /home/app +RUN mkdir -p /var/log/flask-app && touch /var/log/flask-app/flask-app.err.log && touch /var/log/flask-app/flask-app.out.log +RUN chown -R nonroot:nonroot /var/log/flask-app +WORKDIR /home/app +USER nonroot + +# copy all the files to the container +COPY --chown=nonroot:nonroot . . + +# venv +ENV VIRTUAL_ENV=/home/app/venv + +# python setup +RUN python -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" +RUN export FLASK_APP=app.py +RUN pip install -r requirements.txt + +# define the port number the container should expose +EXPOSE 5000 + +CMD ["python", "app.py"] diff --git a/flask/app.py b/flask/app.py new file mode 100644 index 0000000..31c8cf0 --- /dev/null +++ b/flask/app.py @@ -0,0 +1,79 @@ +import os +import requests +from pprint import pprint +from flask import Flask, render_template +import datetime + + +API_KEY = os.environ['API_KEY'] + +API_URL = "https://api.golemio.cz/v2/pid/departureboards" + +STOPS = ["U632Z3P", "U436Z1P"] + +#STOPS = ["U632Z3P"] + +CAR_AMOUNT = 10 + +app = Flask(__name__) + + +def get_departures(api, key, stop): + headers = {"accept": "application/json", "X-Access-Token": key} + params = {"ids": stop, "limit": CAR_AMOUNT} + r = requests.get(api, params=params, headers=headers) + return r.json() + + +def get_cars(stops): + cars = [] + for stop in stops: + deps = get_departures(API_URL, API_KEY, stop) + stop_name = deps["stops"][0]["stop_name"] + stop_platform = deps["stops"][0]["platform_code"] + stop_display = stop_name #+ " (" + stop_platform + ")" + + stop_cars = [] + + for car in deps["departures"]: + # print(car) + car_id = car["route"]["short_name"] + car_dir = car["trip"]["headsign"] + car_ac = car["trip"]["is_air_conditioned"] + car_departure = car["departure_timestamp"]["minutes"] + if car["delay"]["is_available"]: + # car_ahead = "+ " + car_delay_sec = (car["delay"]["minutes"] * 60) + car["delay"]["seconds"] + # if car_delay_sec < 0: + # car_ahead = "- " + # elif car_delay_sec == 0: + # car_ahead = "" + + + # m, s = divmod(car_delay_sec, 60) + # car_delay = car_ahead + str(m) + "min, " + str(s) + "sec" + car_delay = car_delay_sec + else: + car_delay = "?" + + car_data = { + "id": car_id, + "dir": car_dir, + "ac": car_ac, + "departure": car_departure, + "delay": car_delay, + } + + stop_cars.append(car_data) + + cars.append({"id": stop, "name": stop_display, "cars": stop_cars}) + return cars + + +@app.route("/") +def hello_world(): + return render_template('index.html', data=get_cars(STOPS)) + +@app.route('/flask-health-check') +def flask_health_check(): + return "success" diff --git a/flask/requirements.txt b/flask/requirements.txt new file mode 100644 index 0000000..0615934 --- /dev/null +++ b/flask/requirements.txt @@ -0,0 +1,3 @@ +requests +gunicorn +Flask diff --git a/flask/templates/index.html b/flask/templates/index.html new file mode 100644 index 0000000..f4a2ffb --- /dev/null +++ b/flask/templates/index.html @@ -0,0 +1,55 @@ + + + + + + + + + + + Hello, world! + + + +
+ + {% for stop in data %} + +

{{ stop.name }}

+ + + + + + + + + + + + + + + {% for car in stop.cars %} + + + + + + + + {% endfor %} + +
LinkaOdjezd zaZpožděníAC
{{ car.id }}{{ car.departure }} min{{ car.delay }} sec{% if car.ac %} {% endif %}
+ + {% endfor %} + Odjezdy jsou včetně zpoždění +
+ + + + + + + diff --git a/flask/wsgi.py b/flask/wsgi.py new file mode 100644 index 0000000..b0efe6e --- /dev/null +++ b/flask/wsgi.py @@ -0,0 +1,5 @@ +from app import app +import os + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=os.environ.get("FLASK_SERVER_PORT"), debug=True) \ No newline at end of file diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 0000000..ce35eee --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,32 @@ +FROM nginx:1.25.2-alpine + +# Add bash for boot cmd +RUN apk add bash + +# Add nginx.conf to container +COPY --chown=nginx:nginx nginx.conf /etc/nginx/nginx.conf +COPY --chown=nginx:nginx start.sh /app/start.sh + +# set workdir +WORKDIR /app + +# permissions and nginx user for tightened security +RUN chown -R nginx:nginx /app && chmod -R 755 /app && \ + chown -R nginx:nginx /var/cache/nginx && \ + chown -R nginx:nginx /var/log/nginx && \ + chmod -R 755 /var/log/nginx; \ + chown -R nginx:nginx /etc/nginx/conf.d +RUN touch /var/run/nginx.pid && chown -R nginx:nginx /var/run/nginx.pid + +# # Uncomment to keep the nginx logs inside the container - Leave commented for logging to stdout and stderr +# RUN mkdir -p /var/log/nginx +# RUN unlink /var/log/nginx/access.log \ +# && unlink /var/log/nginx/error.log \ +# && touch /var/log/nginx/access.log \ +# && touch /var/log/nginx/error.log \ +# && chown nginx /var/log/nginx/*log \ +# && chmod 644 /var/log/nginx/*log + +USER nginx + +CMD ["nginx", "-g", "'daemon off;'"] \ No newline at end of file diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000..c8a8bcd --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,18 @@ +proxy_cache_path /tmp/cache levels=1:2 keys_zone=cache:10m max_size=500m inactive=60m use_temp_path=off; + +server { + listen 80; + + location / { + proxy_pass http://$FLASK_SERVER_ADDR; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /health-check { + add_header Content-Type text/plain; + return 200 "success"; + } + +} \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..d72a39b --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,50 @@ +worker_processes auto; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Define the format of log messages. + log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + '"$host" sn="$server_name" ' + 'rt=$request_time ' + 'ua="$upstream_addr" us="$upstream_status" ' + 'ut="$upstream_response_time" ul="$upstream_response_length" ' + 'cs=$upstream_cache_status' ; + + access_log /var/log/nginx/access.log main_ext; + error_log /var/log/nginx/error.log warn; + + sendfile on; + + keepalive_timeout 65; + + # Enable Compression + gzip on; + + # Disable Display of NGINX Version + server_tokens off; + + # Size Limits + client_body_buffer_size 10K; + client_header_buffer_size 1k; + client_max_body_size 8m; + large_client_header_buffers 2 1k; + + # # SSL / TLS Settings - Suggested for Security + # ssl_protocols TLSv1.2 TLSv1.3; + # ssl_session_timeout 15m; + # ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; + # ssl_prefer_server_ciphers on; + # ssl_session_tickets off; + + include /etc/nginx/conf.d/*.conf; + +} \ No newline at end of file diff --git a/nginx/start.sh b/nginx/start.sh new file mode 100644 index 0000000..d3c7ac2 --- /dev/null +++ b/nginx/start.sh @@ -0,0 +1,2 @@ +#!/bin/bash +envsubst '$FLASK_SERVER_ADDR' < /tmp/default.conf > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;' \ No newline at end of file