From 580bcb704f6e073a3e7ca9aa4645e56224724cd1 Mon Sep 17 00:00:00 2001 From: Andrew Simonson Date: Sun, 29 Jan 2023 11:02:30 -0600 Subject: [PATCH 1/8] added nginx with supervisor so much suffering occurred here. Let's hope it was worth it. --- Dockerfile | 37 +++++++++++++++++++++++++++++-------- flask.conf | 9 +++++++++ gunicorn.conf | 3 +++ supervisord.conf | 19 +++++++++++++++++++ 4 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 flask.conf create mode 100644 gunicorn.conf create mode 100644 supervisord.conf diff --git a/Dockerfile b/Dockerfile index 1b45ca9..a9a6b50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,34 @@ -FROM docker.io/python:3.8-buster +FROM ubuntu:lunar LABEL maintainer="Andrew Simonson " -WORKDIR /app -ADD ./src /app +ENV DEBIAN_FRONTEND noninteractive -COPY . . +RUN apt-get update +RUN apt-get install -y python3-pip nginx gunicorn supervisor -RUN apt-get -yq update && \ - pip install --no-cache-dir -r ./src/requirements.txt -WORKDIR /app/src +# Setup flask application +RUN mkdir -p /deploy/app +COPY src /deploy/app +RUN pip install -r /deploy/app/requirements.txt -CMD [ "gunicorn", "--bind", "0.0.0.0:8080", "app:app"] \ No newline at end of file +# Setup nginx +RUN rm /etc/nginx/sites-enabled/default +COPY flask.conf /etc/nginx/sites-available/ +RUN ln -s /etc/nginx/sites-available/flask.conf /etc/nginx/sites-enabled/flask.conf && \ + echo "daemon off;" >> /etc/nginx/nginx.conf + +# Setup supervisord +RUN mkdir -p /var/log/supervisor +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY gunicorn.conf /etc/supervisor/conf.d/gunicorn.conf + +# Permissions +# RUN adduser --disabled-password --gecos '' supervisor && \ +RUN chmod -R 777 /var/* && \ + chown -R root /var/* + +# Entrypoint +USER root + +# Start processes +CMD ["/usr/bin/supervisord"] diff --git a/flask.conf b/flask.conf new file mode 100644 index 0000000..449eab3 --- /dev/null +++ b/flask.conf @@ -0,0 +1,9 @@ +server { + listen 8080; + + location / { + proxy_pass http://localhost:5000/; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} diff --git a/gunicorn.conf b/gunicorn.conf new file mode 100644 index 0000000..6412df9 --- /dev/null +++ b/gunicorn.conf @@ -0,0 +1,3 @@ +[program:gunicorn] +command=/usr/bin/gunicorn app:app -b localhost:5000 +directory=/deploy/app diff --git a/supervisord.conf b/supervisord.conf new file mode 100644 index 0000000..1ff0908 --- /dev/null +++ b/supervisord.conf @@ -0,0 +1,19 @@ +[supervisord] +nodaemon=true +redirect_stderr=true +stdout_logfile=/dev/null +username = dummy +password = dummy + +[program:nginx] +command=/usr/sbin/nginx +redirect_stderr=true +stdout_logfile=/dev/null + +[unix_http_server] +username = dummy +password = dummy + +[supervisorctl] +username = dummy +password = dummy \ No newline at end of file From 1a7612a9765b36855e845a01cdd32e963771b3d2 Mon Sep 17 00:00:00 2001 From: Andrew Simonson Date: Sun, 29 Jan 2023 11:11:08 -0600 Subject: [PATCH 2/8] nginx http and www redirects --- flask.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flask.conf b/flask.conf index 449eab3..3afc627 100644 --- a/flask.conf +++ b/flask.conf @@ -1,6 +1,10 @@ server { listen 8080; + server_name _; + + return 301 https://asimonson.com$request_uri + location / { proxy_pass http://localhost:5000/; proxy_set_header Host $host; From 16ec6f7a92b585f08e59b82b7b751155dc243ad6 Mon Sep 17 00:00:00 2001 From: Andrew Simonson Date: Sun, 29 Jan 2023 11:16:38 -0600 Subject: [PATCH 3/8] add nginx security policies --- flask.conf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask.conf b/flask.conf index 3afc627..82c9ee8 100644 --- a/flask.conf +++ b/flask.conf @@ -2,8 +2,10 @@ server { listen 8080; server_name _; + add_header Content-Security-Policy "default-src 'self';"; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - return 301 https://asimonson.com$request_uri + return 301 https://asimonson.com$request_uri; location / { proxy_pass http://localhost:5000/; From e9d9d4baed55538cbcb25a399a8450d0ace91162 Mon Sep 17 00:00:00 2001 From: Andrew Simonson Date: Sun, 29 Jan 2023 12:27:55 -0600 Subject: [PATCH 4/8] fix security policies --- flask.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flask.conf b/flask.conf index 82c9ee8..58fa085 100644 --- a/flask.conf +++ b/flask.conf @@ -1,15 +1,15 @@ server { listen 8080; - server_name _; - add_header Content-Security-Policy "default-src 'self';"; + server_name asimonson.com; + add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' *.chesscomfiles.com *.chess.com *.googletagmanager.com cdn.jsdelivr.net www.google-analytics.com ajax.googleapis.com;"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - return 301 https://asimonson.com$request_uri; - location / { proxy_pass http://localhost:5000/; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } + + return 301 https://$server_name$request_uri; } From f99ad56e1ddc89991b02ad56b7fc593ed6526271 Mon Sep 17 00:00:00 2001 From: Andrew Simonson Date: Sun, 29 Jan 2023 14:25:39 -0600 Subject: [PATCH 5/8] bug squashing --- flask.conf | 12 +++++++++--- src/static/css/App.css | 2 +- .../fonts/{Neon Future.ttf => NeonFuture.ttf} | Bin src/static/json/projects.json | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) rename src/static/fonts/{Neon Future.ttf => NeonFuture.ttf} (100%) diff --git a/flask.conf b/flask.conf index 58fa085..ea4d07c 100644 --- a/flask.conf +++ b/flask.conf @@ -1,9 +1,16 @@ server { listen 8080; - + server_name www.asimonson.com; + return 301 https://asimonson.com$request_uri; +} +server { + listen 8080; server_name asimonson.com; + add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' *.chesscomfiles.com *.chess.com *.googletagmanager.com cdn.jsdelivr.net www.google-analytics.com ajax.googleapis.com;"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Content-Type-Options 'nosniff'; + add_header X-Frame-Options 'SAMEORIGIN'; location / { proxy_pass http://localhost:5000/; @@ -11,5 +18,4 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } - return 301 https://$server_name$request_uri; -} +} \ No newline at end of file diff --git a/src/static/css/App.css b/src/static/css/App.css index 77710ff..dc5fdf4 100644 --- a/src/static/css/App.css +++ b/src/static/css/App.css @@ -1,6 +1,6 @@ @font-face { font-family: "neon-future"; - src: url("../fonts/Neon Future.ttf") + src: url("../fonts/NeonFuture.ttf") } @font-face { diff --git a/src/static/fonts/Neon Future.ttf b/src/static/fonts/NeonFuture.ttf similarity index 100% rename from src/static/fonts/Neon Future.ttf rename to src/static/fonts/NeonFuture.ttf diff --git a/src/static/json/projects.json b/src/static/json/projects.json index 46eb7c9..2ac76b8 100644 --- a/src/static/json/projects.json +++ b/src/static/json/projects.json @@ -37,7 +37,7 @@ "content": "My Resume, made in LaTeX with a custom design derived by the AltaCV template on OverLeaf", "links": [ ["github", "https://github.com/asimonson1125/Resume", "git repo"], - ["globe", "https://asimonson.com/Resume.pdf/", "Resume"] + ["globe", "https://asimonson.com/Resume.pdf", "Resume"] ] }, "Digital Portfolio": { From 8b4a97bf738304ca63b7e6b64df5a50d22d3a113 Mon Sep 17 00:00:00 2001 From: Andrew Simonson Date: Sun, 29 Jan 2023 18:04:16 -0600 Subject: [PATCH 6/8] simulate Single Page App with websockets --- flask.conf | 2 +- gunicorn.conf | 2 +- src/app.py | 81 +++++++++++++++++++++++------------- src/requirements.txt | Bin 930 -> 1366 bytes src/static/css/head.css | 2 +- src/static/js/responsive.js | 23 ++++++++++ src/templates/about.html | 10 ++--- src/templates/error.html | 2 +- src/templates/header.html | 23 +++++----- src/templates/home.html | 11 +++-- src/templates/projects.html | 6 +-- 11 files changed, 105 insertions(+), 57 deletions(-) diff --git a/flask.conf b/flask.conf index ea4d07c..218e6d6 100644 --- a/flask.conf +++ b/flask.conf @@ -7,7 +7,7 @@ server { listen 8080; server_name asimonson.com; - add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' *.chesscomfiles.com *.chess.com *.googletagmanager.com cdn.jsdelivr.net www.google-analytics.com ajax.googleapis.com;"; + add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' *.cloudflare.com *.chesscomfiles.com *.chess.com *.googletagmanager.com cdn.jsdelivr.net www.google-analytics.com ajax.googleapis.com;"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options 'nosniff'; add_header X-Frame-Options 'SAMEORIGIN'; diff --git a/gunicorn.conf b/gunicorn.conf index 6412df9..334b24e 100644 --- a/gunicorn.conf +++ b/gunicorn.conf @@ -1,3 +1,3 @@ [program:gunicorn] -command=/usr/bin/gunicorn app:app -b localhost:5000 +command=/usr/bin/gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker app:app -b localhost:5000 directory=/deploy/app diff --git a/src/app.py b/src/app.py index 903cfbc..43c4f1e 100644 --- a/src/app.py +++ b/src/app.py @@ -1,44 +1,62 @@ import flask from flask_minify import Minify +from flask_socketio import SocketIO import json -proj = json.load(open('./static/json/projects.json', 'r')) -timeline = json.load(open('./static/json/timeline.json', 'r')) +proj = json.load(open("./static/json/projects.json", "r")) +timeline = json.load(open("./static/json/timeline.json", "r")) +pages = { + "home": { + "template": "home.html", + "title": "Andrew Simonson - Portfolio Home", + "description": "Andrew Simonson's Digital Portfolio home", + "canonical": "", + }, + "projects": { + "template": "projects.html", + "projects": proj, + "title": "Andrew Simonson - Projects", + "description": "Recent projects by Andrew Simonson on his lovely portfolio website :)", + "canonical": "projects", + }, + "about": { + "template": "about.html", + "timeline": timeline, + "title": "Andrew Simonson - About Me", + "description": "About Andrew Simonson", + "canonical": "about", + }, +} app = flask.Flask(__name__) Minify(app=app, html=True, js=True, cssless=True) +socketio = SocketIO(app) + + +@socketio.on("goto") +def goto(location): + sid = flask.request.sid + pagevars = pages[location] + output = [location, flask.render_template(pagevars["template"], var=pagevars), pagevars['title']] + socketio.emit("goto", output, to=sid) @app.route("/") def home(): - return flask.render_template( - "home.html", - title="Andrew Simonson - Portfolio Home", - description="Andrew Simonson's Digital portfolio home", - canonical="", - ) - - -@app.route("/about") -def about(): - return flask.render_template( - "about.html", - timeline=timeline, - title="Andrew Simonson - About Me", - description="About Andrew Simonson", - canonical="about", - ) + pagevars = pages["home"] + return flask.render_template("header.html", var=pagevars) @app.route("/projects") def projects(): - return flask.render_template( - "projects.html", - projects=proj, - title="Andrew Simonson - Projects", - description="Recent projects by Andrew Simonson on his lovely portfolio website :)", - canonical="projects", - ) + pagevars = pages["projects"] + return flask.render_template("header.html", var=pagevars) + + +@app.route("/about") +def about(): + pagevars = pages["about"] + return flask.render_template("header.html", var=pagevars) @app.route("/resume") @@ -54,9 +72,16 @@ def page404(e): try: message = e.length finally: + pagevars = { + "template": "error.html", + "title": f"{eCode} - Simonson", + "description": "Error on Andrew Simonson's Digital Portfolio", + "canonical": "404", + } return ( flask.render_template( - "error.html", + "header.html", + var=pagevars, error=eCode, message=message, title=f"{eCode} - Simonson Portfolio", @@ -75,4 +100,4 @@ if __name__ == "__main__": import sass sass.compile(dirname=("static/scss", "static/css"), output_style="compressed") - app.run(debug=True) + socketio.run(app) diff --git a/src/requirements.txt b/src/requirements.txt index 1246f6ba6cccf7e63d1958116fba21bb77ccc6c9..5e1ed2b15fa0695af05da0ea667d125edf78b98b 100644 GIT binary patch delta 439 zcmY+Au}*_v6orp0CM1rgdlN^9Agytzqlr^Z9GxfvB_&9Zw5dx+XVSslH*j+BJ^BhA zdc=($k!80pBi3!goY`8o&9Gt;KW!#=eh^UaRqwgcR ztDE=D9a8vs#R3){@j}P~Awl<;s$7IuELTx6drMZ$1gp { + pagename = page[0]; + content = page[1]; + let root = document.getElementById('root'); + root.innerHTML = content; + root.querySelectorAll("script").forEach(x => { + eval(x.innerHTML); + }); + document.querySelector('title').textContent = page[2]; + if (pagename == 'home') pagename = '/'; + history.pushState(null, null, pagename); +}); \ No newline at end of file diff --git a/src/templates/about.html b/src/templates/about.html index 8b5b006..933371a 100644 --- a/src/templates/about.html +++ b/src/templates/about.html @@ -1,4 +1,4 @@ -{% extends "header.html" %} {% block content %} +{% block content %}
@@ -41,10 +41,10 @@

Skills

{% from 'partials/skills.html' import skills %} - {{ skills([ "Python", "JavaScript", "Java", "C", "C++", "MIPS Assembly", + {{ skills([ "Python", "JavaScript", "Java", "C", "C++", "R", "MIPS Assembly", "Processing", "P5.js", "SQL", "SQLite", "PostgreSQL", "SQLAlchemy", "HTML", "CSS", "Docker", "LaTeX", "ArcGIS", "Git", "Github", "Linux", - "OKD4", "Kubernetes", "Angular", "Flask", "Jinja", "DOM Scraping", + "OKD4", "Kubernetes", "Openshift", "Angular", "Flask", "Jinja", "DOM Scraping", "Google API", "React", "Node.js", "ArcGIS", ]) }}
@@ -121,8 +121,8 @@
{% from 'partials/timeline.html' import timeitem %} - {% for i in timeline %} - {{ timeitem(i, timeline[i]["classes"], timeline[i]["date"], timeline[i]["content"])}} + {% for i in var["timeline"] %} + {{ timeitem(i, var["timeline"][i]["classes"], var["timeline"][i]["date"], var["timeline"][i]["content"])}} {% endfor %}
diff --git a/src/templates/error.html b/src/templates/error.html index bc962ec..d94850c 100644 --- a/src/templates/error.html +++ b/src/templates/error.html @@ -1,4 +1,4 @@ -{% extends "header.html" %} {% block content %} +{% block content %}
diff --git a/src/templates/header.html b/src/templates/header.html index 00d3b00..021228a 100644 --- a/src/templates/header.html +++ b/src/templates/header.html @@ -5,22 +5,22 @@ - + - + - + @@ -52,13 +52,14 @@ href="{{ url_for('static', filename='css/head.css') }}" /> - + + - {{ title }} + {{ var['title'] }} @@ -87,23 +88,23 @@
-
{% block content %}{% endblock %}
+
{% include var['template'] %}
diff --git a/src/templates/home.html b/src/templates/home.html index f0c9198..e7f692f 100644 --- a/src/templates/home.html +++ b/src/templates/home.html @@ -1,4 +1,4 @@ -{% extends "header.html" %} {% block content %} +{% block content %}
@@ -8,14 +8,13 @@
- + />
- {% from 'partials/chess.html' import chess %} - {{ chess('asimonson1125') }} + {% from 'partials/chess.html' import chess %} {{ chess('asimonson1125') + }}
diff --git a/src/templates/projects.html b/src/templates/projects.html index 0d4ab1d..03456a4 100644 --- a/src/templates/projects.html +++ b/src/templates/projects.html @@ -1,4 +1,4 @@ -{% extends "header.html" %} {% block content %} +{% block content %}

Projects

@@ -37,8 +37,8 @@
{% from 'partials/project.html' import project %} - {% for i in projects %} - {{ project(i, projects[i]["classes"], projects[i]["status"], projects[i]["bgi"], projects[i]["content"], projects[i]["links"]) }} + {% for i in var["projects"] %} + {{ project(i, var["projects"][i]["classes"], var["projects"][i]["status"], var["projects"][i]["bgi"], var["projects"][i]["content"], var["projects"][i]["links"]) }} {% endfor %}
From d79868c45ed0325b4ebe580929d88d7f1d89083a Mon Sep 17 00:00:00 2001 From: Andrew Simonson Date: Sun, 29 Jan 2023 18:13:01 -0600 Subject: [PATCH 7/8] allow eval to be used in container --- flask.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask.conf b/flask.conf index 218e6d6..2273c74 100644 --- a/flask.conf +++ b/flask.conf @@ -7,7 +7,7 @@ server { listen 8080; server_name asimonson.com; - add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' *.cloudflare.com *.chesscomfiles.com *.chess.com *.googletagmanager.com cdn.jsdelivr.net www.google-analytics.com ajax.googleapis.com;"; + add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' *.cloudflare.com *.chesscomfiles.com *.chess.com *.googletagmanager.com cdn.jsdelivr.net www.google-analytics.com ajax.googleapis.com;"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options 'nosniff'; add_header X-Frame-Options 'SAMEORIGIN'; From 90725adc2d821e7bfbf663aa9cbbc43a96e7a8a1 Mon Sep 17 00:00:00 2001 From: Andrew Simonson Date: Mon, 30 Jan 2023 13:05:04 -0600 Subject: [PATCH 8/8] validation debug --- src/static/css/App.css | 1 + src/templates/header.html | 8 +++----- src/templates/home.html | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/static/css/App.css b/src/static/css/App.css index dc5fdf4..a300a6a 100644 --- a/src/static/css/App.css +++ b/src/static/css/App.css @@ -114,6 +114,7 @@ a { .name { display: inline; + color: #a0a0a0a0; background: url("../photos/sun.png") 0/5rem no-repeat; background-size: contain; padding-left: 5.5rem; diff --git a/src/templates/header.html b/src/templates/header.html index 021228a..c4883ba 100644 --- a/src/templates/header.html +++ b/src/templates/header.html @@ -13,7 +13,7 @@ property="og:image" content="https://asimonson.com{{ url_for('static', filename='photos/sun.png') }}" /> - + @@ -67,8 +67,7 @@
- -
+
{% for i in range(9) %}
@@ -77,8 +76,7 @@ {% endfor %}
-
- + menu
{% from 'partials/chess.html' import chess %} {{ chess('asimonson1125')