enhance front build perf + update Dockerfile

* add vite config to avoir useless resolving
* add asyncio for better perf
* Dockerfile we build front and remove client folder from container
This commit is contained in:
Jordan Blasenhauer 2024-07-22 14:50:48 +02:00
parent ec592a2eec
commit 7f6e6234af
7 changed files with 146 additions and 106 deletions

6
.gitignore vendored
View file

@ -10,4 +10,8 @@ node_modules
.cache/
/package*.json
/src/client/static
/src/client/templates
/src/client/templates
/src/ui/static/assets
/src/ui/static/css
/src/ui/static/flags
/src/ui/static/img

View file

@ -21,6 +21,9 @@ RUN export MAKEFLAGS="-j$(nproc)" && \
pip install --no-cache-dir --require-hashes --break-system-packages -r /tmp/requirements-deps.txt && \
pip install --no-cache-dir --require-hashes --target deps/python $(for file in $(ls /tmp/req/requirements*.txt) ; do echo "-r ${file}" ; done | xargs)
# Install node and npm to build vite frontend
RUN apk add --no-cache nodejs npm
# Copy files
# can't exclude specific files/dir from . so we are copying everything by hand
COPY src/common/api api
@ -34,6 +37,13 @@ COPY src/common/templates templates
COPY src/ui ui
COPY src/VERSION VERSION
# This will build the frontend and add files to the UI folder
WORKDIR /usr/share/bunkerweb/ui/client
RUN python3 build.py
# We can delete the client folder after building the frontend
RUN rm -rf /usr/share/bunkerweb/ui/client
FROM python:3.12.4-alpine3.19@sha256:ef3397d09070efd36583e83d2619cf8006158641e5b6b629d4d92a9778f5aa1c
# Set default umask to prevent huge recursive chmod increasing the final image size
@ -53,7 +63,7 @@ RUN apk add --no-cache bash unzip libmagic mariadb-connector-c mariadb-client po
mkdir -p /data/cache && ln -s /data/cache /var/cache/bunkerweb && \
mkdir -p /data/lib && ln -s /data/lib /var/lib/bunkerweb && \
for dir in $(echo "pro configs plugins") ; do mkdir -p "/data/${dir}" && ln -s "/data/${dir}" "/etc/bunkerweb/${dir}" ; done && \
for dir in $(echo "pro/plugins configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs configs/crs-plugins-before configs/crs-plugins-after") ; do mkdir "/data/${dir}" ; done && \
for dir in $(echo "pro/plugins configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir "/data/${dir}" ; done && \
chown -R root:ui INTEGRATION /data /var/cache/bunkerweb /var/lib/bunkerweb /etc/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb && \
chmod -R 770 /data /var/cache/bunkerweb /var/lib/bunkerweb /etc/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb && \
chmod 750 gen/*.py ui/*.py ui/src/*.py helpers/*.sh deps/python/bin/* && \
@ -66,7 +76,7 @@ RUN apk add --no-cache "busybox>=1.36.1-r17" "busybox-binsh>=1.36.1-r17" "ssl_cl
RUN apk add --no-cache "libcrypto3>=3.1.6-r0" "libssl3>=3.1.6-r0" # CVE-2024-4741 CVE-2024-5535
LABEL maintainer="Bunkerity <contact@bunkerity.com>"
LABEL version="1.6.0-beta"
LABEL version="1.5.9"
LABEL url="https://www.bunkerweb.io"
LABEL bunkerweb.type="ui"

View file

@ -3,6 +3,7 @@ from subprocess import Popen, PIPE
import os
import shutil
import re
import asyncio
# get current directory
current_directory = os.path.dirname(os.path.realpath(__file__))
@ -19,6 +20,21 @@ ui_dir_templates = f"{current_directory}/../templates"
statics = ("assets", "css", "flags", "img", "js")
def reset():
asyncio.run(remove_dir(opt_dir))
asyncio.run(remove_dir(opt_dir_dashboard))
asyncio.run(remove_dir(opt_dir_setup))
def set_dashboard():
move_template(opt_dir_dashboard_pages, ui_dir_templates)
move_statics(opt_dir_dashboard, ui_dir_static)
def set_setup():
move_template(opt_dir_setup_page, ui_dir_templates)
def run_command(command, need_wait=False):
process = Popen(command, stdout=PIPE, stderr=PIPE, cwd=current_directory, shell=True)
if need_wait:
@ -26,24 +42,22 @@ def run_command(command, need_wait=False):
out, err = process.communicate()
if err:
print("Error: ", err)
print(out)
print(err)
def remove_dir(directory):
async def remove_dir(directory):
if os.path.exists(directory):
shutil.rmtree(directory)
def reset():
remove_dir(opt_dir)
remove_dir(opt_dir_dashboard)
remove_dir(opt_dir_setup)
async def create_dir(directory):
if not os.path.exists(directory):
os.makedirs(directory, exist_ok=True)
def create_base_dirs():
os.makedirs(opt_dir, exist_ok=True)
os.makedirs(opt_dir_templates, exist_ok=True)
asyncio.run(create_dir(opt_dir))
asyncio.run(create_dir(opt_dir_dashboard))
def move_template(folder, target_folder):
@ -63,67 +77,70 @@ def move_template(folder, target_folder):
</body>
</html>"""
async def move_template_file(root, file, target_folder, base_html):
file_path = os.path.join(root, file)
def format_template(m):
replace = m.group(0).replace('href="/', 'href="').replace('src="/', 'src="')
if ".js" in replace:
replace = ' nonce="{{ script_nonce }}" ' + replace
return replace
# get file content
content = ""
with open(file_path, "r") as f:
content = f.read()
content = re.sub(r'(href|src)="\/(css|js|img|favicon|assets|js)\/[^<]*?(?=<|\/>)', format_template, content)
# get the content before <body>
content = content[: content.index("<body>")] + base_html
# write the new content
with open(file_path, "w") as f:
f.write(content)
# remove previous file if exists
if os.path.exists(f"{target_folder}/{os.path.basename(root)}.html"):
os.remove(f"{target_folder}/{os.path.basename(root)}.html")
shutil.copy(file_path, f"{target_folder}/{os.path.basename(root)}.html")
# I want to run this asynchronusly
# I want to get all subfollder of a folder
for root, dirs, files in os.walk(folder):
for file in files:
file_path = os.path.join(root, file)
# get file content
content = ""
with open(file_path, "r") as f:
content = f.read()
# I want to replace all paths using regex like this : /href="\/(css|js|img|favicon|assets|js)|src="\/(assets|js)/g
regex_paths = """/href="\/(css|js|img|favicon|assets|js)|src="\/(assets|js)/g"""
content = re.sub(regex_paths, "", content)
regex_script = """/<script/g"""
content = re.sub(regex_script, '<script nonce="{{ script_nonce }}" ', content)
# get index of <body>
index = content.index("<body>")
# get the content before <body>
content = content[:index] + base_html
# write the new content
with open(file_path, "w") as f:
f.write(content)
# remove previous file if exists
if os.path.exists(f"{target_folder}/{os.path.basename(root)}.html"):
os.remove(f"{target_folder}/{os.path.basename(root)}.html")
shutil.copy(file_path, f"{target_folder}/{os.path.basename(root)}.html")
asyncio.run(move_template_file(root, file, target_folder, base_html))
def move_statics(folder, target_folder):
async def move_static_file(root, dir, target_folder):
dir = os.path.join(root, dir)
# remove previous folder if exists
if os.path.exists(f"{target_folder}/{os.path.basename(dir)}"):
shutil.rmtree(f"{target_folder}/{os.path.basename(dir)}")
# rename index.html by the name of the folder
shutil.move(dir, f"{target_folder}/{os.path.basename(dir)}")
# I want to get all subfollder of a folder
for root, dirs, files in os.walk(folder):
for dir in dirs:
if dir not in statics:
continue
dir = os.path.join(root, dir)
# remove previous folder if exists
if os.path.exists(f"{target_folder}/{os.path.basename(dir)}"):
shutil.rmtree(f"{target_folder}/{os.path.basename(dir)}")
# rename index.html by the name of the folder
shutil.move(dir, f"{target_folder}/{os.path.basename(dir)}")
def set_dashboard():
move_template(opt_dir_dashboard_pages, ui_dir_templates)
move_statics(opt_dir_dashboard, ui_dir_static)
def set_setup():
move_template(opt_dir_setup_page, ui_dir_templates)
asyncio.run(move_static_file(root, dir, target_folder))
def build():
reset()
create_base_dirs()
run_command(["npm", "install"], True)
# Check if node modules exists
if not os.path.exists(f"{current_directory}/node_modules"):
run_command(["npm", "install"], True)
# Create the build
run_command(["npm", "run", "build-dashboard"], True)
set_dashboard()
# run_command(["npm", "run", "build-dashboard"])
# run_command(["npm", "run", "build-setup"], True)
set_dashboard()
# set_setup()

View file

@ -17,6 +17,9 @@ export default defineConfig({
port: 3000,
},
resolve: {
// https://vitejs.dev/config/#resolve-extensions
// Reduce the amount of extensions that Vite will try to resolv
extensions: [".js", ".json", ".vue", ".css"],
alias: {
"@": resolve(__dirname, "./dashboard"),
"@store": resolve(__dirname, "./dashboard/store"),
@ -30,6 +33,7 @@ export default defineConfig({
},
},
build: {
minify: "esbuild",
chunkSizeWarningLimit: 1024,
outDir: "./opt-dashboard",
emptyOutDir: "./opt-dashboard",

View file

@ -14,6 +14,9 @@ export default defineConfig({
}),
],
resolve: {
// https://vitejs.dev/config/#resolve-extensions
// Reduce the amount of extensions that Vite will try to resolve
extensions: [".js", ".json", ".vue", ".css"],
alias: {
"@store": resolve(__dirname, "./dashboard/store"),
"@utils": resolve(__dirname, "./dashboard/utils"),
@ -25,6 +28,8 @@ export default defineConfig({
},
},
build: {
minify: "esbuild",
chunkSizeWarningLimit: 1024,
outDir: "./opt-setup",
emptyOutDir: "./opt-setup",
rollupOptions: {

View file

@ -1,27 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="img/favicon.ico" />
<link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="css/flag-icons.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BunkerWeb | DASHBOARD</title>
<script nonce="{{ script_nonce }}" type="module" crossorigin src="assets/home-098d38b3.js"></script>
<link rel="modulepreload" crossorigin href="assets/Title-9ae7a316.js">
</head>
<body>
{% set data_server_flash = [] %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
{% if data_server_flash.append({"type": "error" if category == "error" else "success", "title": "dashboard_error" if category == "error" else "dashboard_success", "message": message}) %}{% endif %}
{% endfor %}
{% endwith %}
<div class='hidden' data-csrf-token='{{ csrf_token() }}'></div>
<div class='hidden' data-server-global='{{data_server_global if data_server_global else {}}}'></div>
<div class='hidden' data-server-flash='{{data_server_flash|tojson}}'></div>
<div class='hidden' data-server-builder='{{data_server_builder[1:-1]}}'></div>
<div id='app'></div>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="img/favicon.ico" />
<link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="css/flag-icons.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BunkerWeb | DASHBOARD</title>
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/home-BnrNYFnz.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Title-B_4IDnhH.js">
</head>
<body>
{% set data_server_flash = [] %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
{% if data_server_flash.append({"type": "error" if category == "error" else "success", "title": "dashboard_error" if category == "error" else "dashboard_success", "message": message}) %}{% endif %}
{% endfor %}
{% endwith %}
<div class='hidden' data-csrf-token='{{ csrf_token() }}'></div>
<div class='hidden' data-server-global='{{data_server_global if data_server_global else {}}}'></div>
<div class='hidden' data-server-flash='{{data_server_flash|tojson}}'></div>
<div class='hidden' data-server-builder='{{data_server_builder[1:-1]}}'></div>
<div id='app'></div>
</body>
</html>

View file

@ -1,27 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="img/favicon.ico" />
<link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="css/flag-icons.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BunkerWeb | DASHBOARD</title>
<script nonce="{{ script_nonce }}" type="module" crossorigin src="assets/instances-32f9ed21.js"></script>
<link rel="modulepreload" crossorigin href="assets/Title-9ae7a316.js">
</head>
<body>
{% set data_server_flash = [] %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
{% if data_server_flash.append({"type": "error" if category == "error" else "success", "title": "dashboard_error" if category == "error" else "dashboard_success", "message": message}) %}{% endif %}
{% endfor %}
{% endwith %}
<div class='hidden' data-csrf-token='{{ csrf_token() }}'></div>
<div class='hidden' data-server-global='{{data_server_global if data_server_global else {}}}'></div>
<div class='hidden' data-server-flash='{{data_server_flash|tojson}}'></div>
<div class='hidden' data-server-builder='{{data_server_builder[1:-1]}}'></div>
<div id='app'></div>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="img/favicon.ico" />
<link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="css/flag-icons.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BunkerWeb | DASHBOARD</title>
<script type="module" crossorigin nonce="{{ script_nonce }}" src="assets/instances-DfV5PghU.js"></script>
<link rel="modulepreload" crossorigin nonce="{{ script_nonce }}" href="assets/Title-B_4IDnhH.js">
</head>
<body>
{% set data_server_flash = [] %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
{% if data_server_flash.append({"type": "error" if category == "error" else "success", "title": "dashboard_error" if category == "error" else "dashboard_success", "message": message}) %}{% endif %}
{% endfor %}
{% endwith %}
<div class='hidden' data-csrf-token='{{ csrf_token() }}'></div>
<div class='hidden' data-server-global='{{data_server_global if data_server_global else {}}}'></div>
<div class='hidden' data-server-flash='{{data_server_flash|tojson}}'></div>
<div class='hidden' data-server-builder='{{data_server_builder[1:-1]}}'></div>
<div id='app'></div>
</body>
</html>