From a482d5bba813cf369201cf6cd7c0c237946fcc5a Mon Sep 17 00:00:00 2001 From: Iain Learmonth Date: Sat, 9 Nov 2024 17:12:34 +0000 Subject: [PATCH] feat: upgrade python version and install frontend in container --- .gitignore | 10 +++++ .gitlab-ci.yml | 2 +- Dockerfile | 61 ++++++++++++++++++------------- Makefile | 14 +++++++ app/__init__.py | 15 +++++++- app/portal/templates/base.html.j2 | 5 ++- 6 files changed, 77 insertions(+), 30 deletions(-) create mode 100644 Makefile diff --git a/.gitignore b/.gitignore index 3618a1e..a87af34 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,16 @@ # Secrets config.yaml +# External frontend repo and build +/frontend/ +/app/static/ui/ + +# build-harness +/build-harness/ +/build-harness-extensions/ +.build-harness +.build-harness-ext + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c9c7d93..ac2f495 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: python:3.8-bullseye +image: python:3.11-bookworm stages: - test diff --git a/Dockerfile b/Dockerfile index cbe9b17..1845455 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,55 +1,64 @@ -#FROM python:3.9.13-slim-bullseye -FROM debian:bullseye AS portal -MAINTAINER Ana Custura +FROM debian:bookworm AS portal ENV APP="bc" -ENV APP_BASE="/srv/" +ENV APP_BASE="/srv" ENV SHELL="/bin/bash" ENV FLASK_APP="${FLASK_APP:-app}" ENV FLASK_RUN_HOST="${FLASK_RUN_HOST:-0.0.0.0}" ENV FLASK_RUN_PORT="${FLASK_RUN_PORT:-5000}" +ENV PYTHONPATH="${APP_BASE}/env/lib/python3.11/site-packages" +ENV PATH="${APP_BASE}/env/bin:/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin:/home/${APP}/.local/bin" -# Set PATH and PYTHONPATH in the container -ENV PYTHONPATH="/usr/lib/python3/dist-packages:/home/${APP}/.local/lib/python3.9/site-packages" -ENV PATH="/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin:/home/${APP}/.local/bin" - -# UID and GID might be read-only values, so use non-conflicting ones ARG CONTAINER_UID="${CONTAINER_UID:-1000}" ARG CONTAINER_GID="${CONTAINER_GID:-1000}" -# Install dependencies RUN apt-get update && \ apt-get install --no-install-recommends -y \ curl \ - software-properties-common \ python3-pip \ + python3-venv \ cron \ git \ - gnupg2 + gnupg && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +ARG OPENTOFU_VERSION="1.8.5" +RUN curl -fsSL https://get.opentofu.org/opentofu.gpg -o opentofu.gpg && \ + gpg --import opentofu.gpg && \ + curl -fsSL https://github.com/opentofu/opentofu/releases/download/v${OPENTOFU_VERSION}/tofu_${OPENTOFU_VERSION}_linux_amd64.tar.gz -o opentofu.tar.gz && \ + curl -fsSL https://github.com/opentofu/opentofu/releases/download/v${OPENTOFU_VERSION}/tofu_${OPENTOFU_VERSION}_linux_amd64.tar.gz.gpgsig -o opentofu.tar.gz.gpgsig && \ + gpg --verify opentofu.tar.gz.gpgsig opentofu.tar.gz && \ + tar -xzf opentofu.tar.gz -C /usr/local/bin tofu && \ + chmod +x /usr/local/bin/tofu && \ + ln -s /usr/local/bin/tofu /usr/local/bin/terraform && \ + rm -rf opentofu.tar.gz opentofu.tar.gz.gpgsig opentofu.gpg /tmp/* /var/tmp/* -# Install Terraform -# See https://www.terraform.io/downloads -RUN /usr/bin/curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add - -RUN apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com bullseye main" RUN apt-get update && \ - apt-get install -y terraform \ - && rm -rf /var/lib/apt/lists/* + apt-get install --no-install-recommends -y curl sudo && \ + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ + apt-get install -y nodejs && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -# Switch to a regular user RUN groupadd -r -g ${CONTAINER_GID} ${APP} && \ useradd --no-log-init -r -u ${CONTAINER_UID} -g ${APP} ${APP} && \ - mkdir -p /home/${APP} && chown -R ${APP}. /home/${APP} -RUN mkdir -p ${APP_BASE}/${APP} && chown ${APP}. ${APP_BASE}/${APP} + mkdir -p /home/${APP} && chown -R ${APP}:${APP} /home/${APP} && \ + mkdir -p ${APP_BASE}/${APP} ${APP_BASE}/env && chown ${APP}:${APP} ${APP_BASE}/${APP} ${APP_BASE}/env + USER ${APP} -# Copy the project into the workdir WORKDIR ${APP_BASE}/${APP} COPY --chown=${APP}:${APP} . ${APP_BASE}/${APP} -# Install Python requirements -RUN pip3 install -r requirements.txt -RUN pip3 install psycopg2-binary +RUN python3 -m venv ${APP_BASE}/env && \ + ${APP_BASE}/env/bin/pip install --no-cache-dir -r requirements.txt && \ + ${APP_BASE}/env/bin/pip install --no-cache-dir psycopg2-binary + +RUN rm -rf frontend && \ + git clone https://gitlab.com/guardianproject-ops/bypass-censorship/portal-frontend.git frontend && \ + cd frontend && npm install && npm run build && \ + mkdir -p ${APP_BASE}/${APP}/app/static/ui && \ + cp -r dist/* ${APP_BASE}/${APP}/app/static/ui && \ + rm -rf frontend /tmp/* /var/tmp/* USER root RUN crontab -u ${APP} docker-crontab -# Set the entrypoint to the web app diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d8f2dfb --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +SHELL := /bin/bash + +export README_TEMPLATE_FILE ?= $(BUILD_HARNESS_EXTENSIONS_PATH)/templates/README.md.gotmpl +export README_DEPS ?= docs/targets.md + +-include $(shell curl -sSL -o .build-harness-ext "https://go.sr2.uk/build-harness"; echo .build-harness-ext) + +.PHONY: install-frontend +# Clone the frontend repo, install dependencies, and build the React app for serving with Flask +install-frontend: + if [ ! -d "frontend" ]; then git clone https://gitlab.com/guardianproject-ops/bypass-censorship/portal-frontend.git frontend; fi + cd frontend && npm install && npm run build + mkdir -p app/static/ui + cp -r frontend/dist/* app/static/ui/ diff --git a/app/__init__.py b/app/__init__.py index f20bf6d..2b650fb 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,7 +1,7 @@ import os from typing import Iterator -from flask import Flask, redirect, url_for +from flask import Flask, redirect, url_for, send_from_directory from flask.typing import ResponseReturnValue from prometheus_client.metrics_core import GaugeMetricFamily, CounterMetricFamily from prometheus_client.registry import Collector @@ -109,8 +109,21 @@ if not_migrating() and 'DISABLE_METRICS' not in os.environ: REGISTRY.register(AutomationCollector()) +@app.route('/ui') +def redirect_ui(): + return redirect("/ui/") + +@app.route('/ui/', defaults={'path': ''}) +@app.route('/ui/') +def serve_ui(path): + if path != "" and os.path.exists("app/static/ui/" + path): + return send_from_directory('static/ui', path) + else: + return send_from_directory('static/ui', 'index.html') + @app.route('/') def index() -> ResponseReturnValue: + # TODO: update to point at new UI when ready return redirect(url_for("portal.portal_home")) diff --git a/app/portal/templates/base.html.j2 b/app/portal/templates/base.html.j2 index d351f46..223f25c 100644 --- a/app/portal/templates/base.html.j2 +++ b/app/portal/templates/base.html.j2 @@ -195,11 +195,13 @@ - {% if config['ADDITIONAL_SIDEBAR_LINKS'] %} - {% endif %}