This commit is contained in:
2024-02-28 10:33:15 +08:00
commit 492c03bfcf
27 changed files with 813 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
# Environment variables for docker-compose.yml
LOG_LEVEL="DEBUG"
NETWORK=web
## dashboard configs
HOST="furyhawk.lol"
# subdomain for dashboard.
DASHBOARD_HOST="dashboard.furyhawk.lol"
# The following are the environment variables for the streamlit app
FIN_LOCATION="/fin"
STREAMLIT_FIN_SERVER_PORT="8501"
BAI_LOCATION="/bai"
STREAMLIT_BAI_SERVER_PORT="8502"
# user/pass
DASHBOARD_USER=admin
DASHBOARD_PASSWORD=pass
OSRM_ALGORITHM="mld"
OSRM_THREADS=2
OSRM_PORT=5000
OSRM_PROFILE="/opt/car.lua"
OSRM_MAP_NAME=${OSRM_MAP_NAME}
OSRM_GEOFABRIK_PATH=${OSRM_GEOFABRIK_PATH}
# Notify OSRM Manager to restart without stopping container
OSRM_NOTIFY_FILEPATH="/data/osrm_notify.txt"
+165
View File
@@ -0,0 +1,165 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
# .env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# ignores production code for traefik and production yml
# compose/traefik/
# production.yml
+23
View File
@@ -0,0 +1,23 @@
# Streamlit + Traefik + Docker
This simple project uses Traefik as a reverse proxy to a Streamlit application and handles SSL certs with Lets Encrypt.
## Requirements
- Docker Compose
- Python 3.9
## Local Deployment
#### Python:
1. `cd src`
2. `pip install -r requirements.txt`
3. `streamlit run app.py`
#### Docker:
1. `sudo docker-compose -f local.yml up --build`
## Production Deployment
1. In `compose/traefik/traefik.yml`, change `example@test.com` to your email.
2. In `compose/traefik/traefik.yml`, change `example.com` to your domain.
3. `docker compose -f production.yml up --build -d --remove-orphans`
### Notes:
Feel free to make a PR or get in contact with me on Discord at yoyojoe#5510.
+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore
BIN
View File
Binary file not shown.
+29
View File
@@ -0,0 +1,29 @@
# base image
FROM python:3.11-slim
#basic build prep
RUN apt-get update && apt-get install -y \
build-essential \
curl \
software-properties-common \
git \
&& rm -rf /var/lib/apt/lists/*
# copy over and install packages
COPY ./src/requirements.txt ./requirements.txt
RUN pip3 install cython
RUN pip3 install -r requirements.txt
# streamlit-specific commands
RUN mkdir -p /root/.streamlit
RUN bash -c 'echo -e "\
[general]\n\
email = \"\"\n\
" > /root/.streamlit/credentials.toml'
# RUN bash -c 'echo -e "\
# [server]\n\
# baseUrlPath = \"/fin\"\n\
# " > /root/.streamlit/config.toml'
# copying everything over
COPY . .
+5
View File
@@ -0,0 +1,5 @@
FROM traefik:v2.11
RUN mkdir -p /etc/traefik/acme \
&& touch /etc/traefik/acme/acme.json \
&& chmod 600 /etc/traefik/acme/acme.json
COPY ./compose/traefik/traefik.yml /etc/traefik
+143
View File
@@ -0,0 +1,143 @@
log:
level: DEBUG
api:
# Dashboard
dashboard: true
# https://docs.traefik.io/master/operations/api/#insecure
# insecure: true
entryPoints:
web:
# http
address: ":80"
http:
# https://docs.traefik.io/routing/entrypoints/#entrypoint
redirections:
entryPoint:
to: web-secure
web-secure:
# https
address: ":443"
# osrm:
# address: ":5000"
certificatesResolvers:
letsencrypt:
# https://docs.traefik.io/master/https/acme/#lets-encrypt
acme:
email: "furyx@hotmail.com"
storage: /etc/traefik/acme/acme.json
# https://docs.traefik.io/master/https/acme/#httpchallenge
httpChallenge:
entryPoint: web
http:
routers:
dashboard:
rule: "Host(`dashboard.furyhawk.lol`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
service: api@internal
middlewares:
- auth
tls:
# https://docs.traefik.io/master/routing/routers/#certresolver
certResolver: letsencrypt
web-secure-router:
rule: "Host(`furyhawk.lol`, `www.furyhawk.lol`, `bai.furyhawk.lol`) || PathPrefix(`/bai`)"
entryPoints:
- web-secure
middlewares:
- csrf
- add-bai
service: streamlit_bai_app
tls:
# https://docs.traefik.io/master/routing/routers/#certresolver
certResolver: letsencrypt
fin-router:
rule: "Host(`fin.furyhawk.lol`)"
entryPoints:
- web-secure
middlewares:
- csrf
- add-fin
service: streamlit_fin_app
tls:
# https://docs.traefik.io/master/routing/routers/#certresolver
certResolver: letsencrypt
blog-router:
rule: "Host(`blog.furyhawk.lol`)"
entryPoints:
- web-secure
# redirect to external blog
middlewares:
- redirect-blog
service: blog
tls:
# https://docs.traefik.io/master/routing/routers/#certresolver
certResolver: letsencrypt
osrm-router:
rule: "Host(`osrm.furyhawk.lol`)"
entryPoints:
- "web-secure"
# - "osrm"
middlewares:
- csrf
service: osrm_service
tls:
# https://docs.traefik.io/master/routing/routers/#certresolver
certResolver: letsencrypt
middlewares:
auth:
basicAuth:
users:
- "test:$apr1$2E4PEW8M$/wEgFNKX71h.YYMywV7WZ/"
csrf:
# https://doc.traefik.io/traefik/middlewares/http/headers/#hostsproxyheaders
# https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
headers:
hostsProxyHeaders: ["X-CSRFToken"]
add-bai:
addPrefix:
prefix: "/bai"
add-fin:
addPrefix:
prefix: "/fin"
redirect-blog:
# https://docs.traefik.io/master/middlewares/redirectscheme/
redirectregex:
regex: "^https://blog.furyhawk.lol/(.*)"
replacement: "https://furyhawk.github.io/124c41/${1}"
permanent: true
services:
osrm_service:
loadBalancer:
servers:
- url: http://osrm_backend:{{env "OSRM_PORT"}}
streamlit_bai_app:
loadBalancer:
servers:
- url: http://streamlit_bai_app:8502/bai
streamlit_fin_app:
loadBalancer:
servers:
- url: http://streamlit_fin_app:{{env "STREAMLIT_FIN_SERVER_PORT"}}/{{env "FIN_LOCATION"}}
blog:
loadBalancer:
servers:
- url: https://furyhawk.github.io/124c41/
providers:
# https://docs.traefik.io/master/providers/file/
file:
filename: /etc/traefik/traefik.yml
watch: true
+3
View File
@@ -0,0 +1,3 @@
*
!.gitignore
!certs.toml
+3
View File
@@ -0,0 +1,3 @@
[tls.stores.default.defaultCertificate]
certFile = "/certs/cert.crt"
keyFile = "/certs/cert.key"
+13
View File
@@ -0,0 +1,13 @@
version: '3.8'
services:
streamlit-fin:
build:
context: .
dockerfile: ./compose/streamlit-fin/Dockerfile
image: streamlit_fin_local
container_name: streamlit_fin_app
restart: always
ports:
- 8501:8501
command: streamlit run src/app.py
+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore
+68
View File
@@ -0,0 +1,68 @@
version: '3.7'
x-environment: &default-environment
LOG_LEVEL: "DEBUG"
DASHBOARD_USER: ${DASHBOARD_USER}
DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD}
FIN_LOCATION: "/fin"
STREAMLIT_FIN_SERVER_PORT: "8501"
BAI_LOCATION: "/bai"
STREAMLIT_BAI_SERVER_PORT: "8502"
volumes:
production_traefik: {}
services:
osrm-backend:
environment:
# OSRM manager setup
- OSRM_ALGORITHM=mld
- OSRM_THREADS=2
- OSRM_PORT=5000
- OSRM_PROFILE=/opt/car.lua
- OSRM_MAP_NAME=${OSRM_MAP_NAME}
- OSRM_GEOFABRIK_PATH=${OSRM_GEOFABRIK_PATH}
# Notify OSRM Manager to restart without stopping container
- OSRM_NOTIFY_FILEPATH=/data/osrm_notify.txt
image: furyhawk/osrm-backend:${OSRM_VERSION:-latest}
container_name: osrm_backend
restart: always
ports:
- ${OSRM_PORT}:${OSRM_PORT}
streamlit-bai:
environment:
<<: *default-environment
image: furyhawk/beyondallinfo:latest
container_name: streamlit_bai_app
restart: always
expose:
- ${STREAMLIT_BAI_SERVER_PORT}
command: streamlit run --server.port=$STREAMLIT_BAI_SERVER_PORT --server.address=0.0.0.0 --server.baseUrlPath=$BAI_LOCATION src/app.py
streamlit-fin:
environment:
<<: *default-environment
build:
context: .
dockerfile: ./compose/streamlit-fin/Dockerfile
image: streamlit_fin_production
container_name: streamlit_fin_app
restart: always
expose:
- ${STREAMLIT_FIN_SERVER_PORT}
command: streamlit run --server.port=$STREAMLIT_FIN_SERVER_PORT --server.address=0.0.0.0 --server.baseUrlPath=$FIN_LOCATION src/app.py
traefik:
environment:
<<: *default-environment
build:
context: .
dockerfile: ./compose/traefik/Dockerfile
image: traefik_production
volumes:
- production_traefik:/etc/traefik/acme:z
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- "0.0.0.0:80:80"
- "0.0.0.0:443:443"
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
set -ev
docker-compose config
docker-compose pull
docker-compose up -d
docker-compose ps
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
set -e
eval $(egrep '^HOST' .env | xargs)
eval $(egrep '^CERT_PATH' .env | xargs)
echo "Domain: ${HOST}"
echo "Cert Path: ${CERT_PATH}"
if [ -f certs/cert.crt ] || [ -f certs/cert.key ] || [ -f certs/cert.pem ]; then
echo -e "cert already exists in certs directory\nDo you want to overwrite the files? [y]es/[n]o"
read -r ANSWER
echo
if [[ "$ANSWER" =~ ^[Yy](es)?$ ]] ; then
echo "Creating Cert"
else
exit 1
fi
fi
./scripts/requests.sh
openssl genrsa -out $CERT_PATH/cert.key
openssl req -new -key $CERT_PATH/cert.key -out $CERT_PATH/cert.csr -config $CERT_PATH/csr.conf
openssl x509 -req -days 365 -in $CERT_PATH/cert.csr -signkey $CERT_PATH/cert.key -out $CERT_PATH/cert.crt -extensions req_ext -extfile $CERT_PATH/csr.conf
sudo cp $CERT_PATH/cert.crt /usr/local/share/ca-certificates/cert.crt
sudo rm -f /usr/local/share/ca-certificates/certificate.crt
# --fresh is needed to remove symlinks to no-longer-present certificates
sudo update-ca-certificates --fresh
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
echo "Cleaning up..."
docker-compose down
printf "Deleting network: "
eval $(egrep '^NETWORK' .env | xargs)
printf "$NETWORK\n"
docker network rm $NETWORK | echo
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# color
RESET=$'\e[1;0m'
RED=$'\e[1;31m'
GREEN=$'\e[1;32m'
YELLOW=$'\e[1;33m'
RED_BACK=$'\e[101m'
GREEN_BACK=$'\e[102m'
YELLOW_BACK=$'\e[103m'
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
set -ev
eval $(egrep '^HOST' .env | xargs)
if [ "$HOST" != "localhost" ]; then
grep "127.0.0.1 ${HOST}" /etc/hosts || (echo "127.0.0.1 ${HOST}" | sudo tee -a /etc/hosts)
fi
grep "127.0.0.1 docker.${HOST}" /etc/hosts || (echo "127.0.0.1 docker.${HOST}" | sudo tee -a /etc/hosts)
grep "127.0.0.1 dashboard.${HOST}" /etc/hosts || (echo "127.0.0.1 dashboard.${HOST}" | sudo tee -a /etc/hosts)
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
set -e
echo "Copying env file"
# Create env from env.example if it doesn't exist
if [ -f ".env" ]
then
echo -e "env file exists"
else
echo -e "Copying env file"
cp env.example .env
fi
echo "creating acme.json"
touch acme.json
chmod 600 acme.json
echo "creating provider.key"
touch provider.key
echo "supersecretkey" | tee provider.key
chmod 600 provider.key
printf "Creating network: "
eval $(egrep '^NETWORK' .env | xargs)
printf "$NETWORK\n"
docker network create $NETWORK | echo
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
set -ex
if [ -z "$DOCKER_COMPOSE_VERSION" ]; then
DOCKER_COMPOSE_VERSION=1.25.4
fi
echo "Installing docker-compose version: $DOCKER_COMPOSE_VERSION"
if [ -z "`sudo -l 2>/dev/null`" ]; then
rm /usr/local/bin/docker-compose | echo
curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
chmod +x docker-compose
mv docker-compose /usr/local/bin
else
sudo rm /usr/local/bin/docker-compose | echo
curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
sudo chmod +x docker-compose
sudo mv docker-compose /usr/local/bin
fi
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
eval $(egrep -v '^#' .env | xargs)
echo "
[req]
default_bits = 2048
distinguished_name = dn
prompt = no
[dn]
C=\"US\"
ST=\"Florida\"
OU=\"Service\"
emailAddress=\"admin@${HOST}\"
CN=\"${HOST}\"
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.0 = ${HOST}
DNS.1 = *.${HOST}
DNS.2 = *.docker.${HOST}
" > certs/csr.conf
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
set -e
export CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
source ./scripts/color.sh
shopt -s expand_aliases
alias curl="curl -ILsS -X GET"
alias grep="grep -C 100 --color=auto"
alias echo="echo -e \${RESET}"
eval $(egrep '^HOST' .env | xargs)
eval $(egrep '^DASHBOARD_HOST' .env | xargs)
echo "\n\n${YELLOW_BACK}${RED}Testing Traefik........................${RESET}\n"
echo "\nHOST=${HOST}"
echo "\nDASHBOARD_HOST=${DASHBOARD_HOST}\n"
echo "\n\n${YELLOW}Rediection test........................${RESET}\n"
echo "\n${GREEN}http://${HOST}${RESET}\n"
curl http://${HOST} | grep 302 || exit 1
echo "\n${GREEN}http://${HOST}${RESET}\n"
curl http://${DASHBOARD_HOST} | grep 302 || exit 1
# echo "\n\nAuthentication test....................\n"
echo "\n\n${YELLOW}Authentication test....................${RESET}\n"
echo "\n${GREEN}https://user:pass@${DASHBOARD_HOST}${RESET}\n"
curl -f --anyauth -u user:pass https://${DASHBOARD_HOST} | grep 200 || exit 1
echo "\n${GREEN}https://user:pass@${DASHBOARD_HOST}/dashboard/${RESET}\n"
curl -f --anyauth -u user:pass https://${DASHBOARD_HOST}/dashboard/ | grep 200 || exit 1
echo "\n\n${GREEN}.......................................${RESET}\n"
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -ev
./scripts/init.sh
./scripts/cert.sh
./scripts/host.sh
./scripts/build.sh
./scripts/wait.sh ${WAIT_FOR}
./scripts/test.sh
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
x="$1"
[[ -z "$x" ]] && x=5
printf "\n\nWaiting for things to start"
while [ $x -gt 0 ]
do
printf "."
sleep 1
x=$(( $x - 1 ))
done
echo "."
BIN
View File
Binary file not shown.
+127
View File
@@ -0,0 +1,127 @@
import streamlit as st
import yfinance as yf
from ta import volume, trend
st.set_page_config(page_title='Technical Analysis',page_icon='📈', layout='wide')
hide_streamlit_style = """
<style>
.reportview-container {
margin-top: -2em;
}
#MainMenu {
visibility: hidden;
}
.stDeployButton {display:none;}
footer {
visibility: hidden;
}
footer:after {
content:'Data Source: Yahoo Finance';
visibility: visible;
display: block;
position: relative;
#background-color: red;
padding: 5px;
top: 2px;
}
</style>
"""
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
stock = st.sidebar.text_input(label="Ticker",value='AAPL')
def get_data(start):
ticker = yf.Ticker(stock)
try:
df = ticker.history(period='max', start=start)
except:
pass
return df
list_of_indicator_types = ['Volume', 'Trend']
volumn_types = ['Volume','Force Index']
trend_types = ["Simple Moving Average", "Exponential Moving Average"]
indicator_types = st.sidebar.selectbox(label='Indicator Type', options = list_of_indicator_types)
st.sidebar.success('All charts are interactive!')
if indicator_types == 'Volume':
indicator = st.selectbox(label='Indicator', options=volumn_types,key=0)
start = st.text_input(label='Start Year', value = '2018')
start = f'{start}-01-01'
df = get_data(start)
if indicator == 'Volume':
df = df[['Close','Volume']]
price_vs_time = st.line_chart(data= df['Close'], width=500, height=400)
volume_vs_time = st.bar_chart(data=df['Volume'], width=500, height=150)
df = df.to_csv()
st.download_button(label=f'Download Data',data=df,file_name=f'{stock}.csv')
if indicator == 'Force Index':
window_slider_expander = st.expander(label='Force Index Parameters')
window_slider = window_slider_expander.slider(label='Window', value=13, min_value=1, max_value=20)
df[f'fi_{window_slider}'] = volume.force_index(close=df['Close'],volume=df['Volume'], window=window_slider)
df = df[['Close','Volume',f'fi_{window_slider}']]
price_vs_time = st.line_chart(data= df['Close'], width=500, height=400)
fi_vs_time = st.area_chart(data= df[f'fi_{window_slider}'],width=500, height=200)
df = df.to_csv()
st.download_button(label=f'Download Data',data=df,file_name=f'{stock}.csv')
if indicator_types == 'Trend':
indicator = st.selectbox(label='Indicator', options=trend_types, key=1)
start = st.text_input(label='Start Year', value = '2018')
start = f'{start}-01-01'
df = get_data(start)
if indicator == 'Simple Moving Average':
window_slider_expander = st.expander(label='SMA Parameters')
window_slider_1 = window_slider_expander.slider(label='Window 1', value=30, min_value=1, max_value=200)
window_slider_2 = window_slider_expander.slider(label='Window 2', value=100, min_value=1, max_value=200)
df[f'sma{window_slider_1}'] = trend.sma_indicator(close=df['Close'], window=window_slider_1)
df[f'sma{window_slider_2}'] = trend.sma_indicator(close=df['Close'], window=window_slider_2)
df = df[['Close',f'sma{window_slider_1}',f'sma{window_slider_2}']]
sma_vs_time = st.line_chart(data=df, width=500, height=550)
df = df.to_csv()
st.download_button(label=f'Download Data',data=df,file_name=f'{stock}.csv')
if indicator == "Exponential Moving Average":
window_slider_expander = st.expander(label='EMA Parameters')
window_slider_1 = window_slider_expander.slider(label='Window 1', value=30, min_value=1, max_value=200)
window_slider_2 = window_slider_expander.slider(label='Window 2', value=100, min_value=1, max_value=200)
df[f'ema{window_slider_1}'] = trend.ema_indicator(close=df['Close'], window=window_slider_1)
df[f'ema{window_slider_2}'] = trend.ema_indicator(close=df['Close'], window=window_slider_2)
df = df[['Close',f'ema{window_slider_1}',f'ema{window_slider_2}']]
sma_vs_time = st.line_chart(data=df, width=500, height=550)
df = df.to_csv()
st.download_button(label=f'Download Data',data=df,file_name=f'{stock}.csv')
+6
View File
@@ -0,0 +1,6 @@
pandas==2.1.3
streamlit==1.28.2
ta==0.11.0
yfinance==0.2.32
watchdog==3.0.0
protobuf==4.25.1