Redo frontend and dockerfile to use nginx and build in stages based on prod steps in romm

This commit is contained in:
2026-01-07 23:05:27 -05:00
parent 9e5f377768
commit 3f9cab66f5
5 changed files with 285 additions and 32 deletions

View File

@@ -1,9 +1,46 @@
FROM cloudron/base:5.0.0@sha256:04fd70dbd8ad6149c19de39e35718e024417c3e01dc9c6637eaf4a41ec4e596c
RUN mkdir -p /app/code
WORKDIR /app/code
ARG VERSION=4.5.0
ARG ALPINE_VERSION=3.22
ARG NODE_VERSION=20.19
# FRONTEND BUILD
FROM node:${NODE_VERSION}-trixie AS frontend-build
ARG VERSION
WORKDIR /front
RUN wget "https://github.com/rommapp/romm/archive/refs/tags/${VERSION}.tar.gz" && \
tar xfz ${VERSION}.tar.gz --strip-components=2 -C /front romm-${VERSION}/frontend && rm ${VERSION}.tar.gz
RUN npm ci --ignore-scripts --no-audit --no-fund
RUN npm run build
# FETCH EMULATORJS AND RUFFLE
FROM alpine:${ALPINE_VERSION} AS emulator-stage
RUN apk add --no-cache \
7zip \
wget \
ca-certificates
ARG EMULATORJS_VERSION=4.2.3
ARG EMULATORJS_SHA256=07d451bc06fa3ad04ab30d9b94eb63ac34ad0babee52d60357b002bde8f3850b
RUN wget "https://github.com/EmulatorJS/EmulatorJS/releases/download/v${EMULATORJS_VERSION}/${EMULATORJS_VERSION}.7z" && \
echo "${EMULATORJS_SHA256} ${EMULATORJS_VERSION}.7z" | sha256sum -c - && \
7z x -y "${EMULATORJS_VERSION}.7z" -o/emulatorjs && \
rm -f "${EMULATORJS_VERSION}.7z"
ARG RUFFLE_VERSION=nightly-2025-08-14
ARG RUFFLE_FILE=ruffle-nightly-2025_08_14-web-selfhosted.zip
ARG RUFFLE_SHA256=178870c5e7dd825a8df35920dfc5328d83e53f3c4d5d95f70b1ea9cd13494151
RUN wget "https://github.com/ruffle-rs/ruffle/releases/download/${RUFFLE_VERSION}/${RUFFLE_FILE}" && \
echo "${RUFFLE_SHA256} ${RUFFLE_FILE}" | sha256sum -c - && \
unzip -o "${RUFFLE_FILE}" -d /ruffle && \
rm -f "${RUFFLE_FILE}"
FROM cloudron/base:5.0.0@sha256:04fd70dbd8ad6149c19de39e35718e024417c3e01dc9c6637eaf4a41ec4e596c as cloudron-builder
ARG NJS_VERSION=0.9.4
ARG MODZIP_VERSION=1.3.0
# Prevent interactive prompts during installation
ENV DEBIAN_FRONTEND=noninteractive
@@ -31,22 +68,19 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
liblzma-dev \
libncurses5-dev \
libncursesw5-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
nginx \
nginx-dev \
libpcre3-dev \
libssl-dev \
libxml2-dev \
libxslt-dev
# Clone release
RUN wget https://github.com/rommapp/romm/archive/refs/tags/$VERSION.tar.gz && \
tar xfz $VERSION.tar.gz --strip-components=1 -C /app/code && rm $VERSION.tar.gz && rm -rf /app/code/backend/romm_test
WORKDIR /
RUN git clone -b ${NJS_VERSION} https://github.com/nginx/njs.git
RUN git clone -b ${MODZIP_VERSION} https://github.com/evanmiller/mod_zip.git
# Install nvm
ENV NVM_DIR="/app/code/.nvm"
RUN mkdir -p $NVM_DIR
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash \
&& . "$NVM_DIR/nvm.sh" \
&& nvm install 18.20.8 \
&& nvm use 18.20.8 \
&& nvm alias default 18.20.8
ENV PATH="$NVM_DIR/versions/node/v18.20.8/bin:$PATH"
WORKDIR /usr/share/nginx/src
RUN ./configure --add-dynamic-module=/njs/nginx --add-dynamic-module=/mod_zip --with-compat --builddir=. && make modules
# Build and install RAHasher (optional for RA hashes)
RUN git clone --recursive --branch 1.8.1 --depth 1 https://github.com/RetroAchievements/RALibretro.git /tmp/RALibretro
@@ -56,20 +90,59 @@ RUN sed -i '22a #include <ctime>' ./src/Util.h \
./src/libchdr/deps/zlib-1.3.1/gzlib.c \
./src/libchdr/deps/zlib-1.3.1/gzread.c \
./src/libchdr/deps/zlib-1.3.1/gzwrite.c \
&& make HAVE_CHD=1 -f ./Makefile.RAHasher \
&& cp ./bin64/RAHasher /usr/bin/RAHasher
RUN rm -rf /tmp/RALibretro
&& make HAVE_CHD=1 -f ./Makefile.RAHasher
# Install frontend dependencies
WORKDIR /app/code/frontend
RUN npm install
RUN ln -s /tmp/vite-temp /app/code/frontend/node_modules/.vite-temp && ln -s /tmp/vite /app/code/frontend/node_modules/.vite && mkdir assets/romm && ln -s /app/data/assets /app/code/frontend/assets/romm/assets && ln -s /app/data/resources /app/code/frontend/assets/romm/resources
COPY --from=docker.io/rommapp/romm:$VERSION /var/www/html/assets/emulatorjs /app/code/frontend/assets/emulatorjs
COPY --from=docker.io/rommapp/romm:$VERSION /var/www/html/assets/ruffle /app/code/frontend/assets/ruffle
# Main image starts here
FROM cloudron/base:5.0.0@sha256:04fd70dbd8ad6149c19de39e35718e024417c3e01dc9c6637eaf4a41ec4e596c
RUN mkdir -p /app/code
WORKDIR /app/code
ARG WEBSERVER_FOLDER=/app/code/frontend
ARG UV_VERSION=0.8.24
ARG VERSION
# Prevent interactive prompts during installation
ENV DEBIAN_FRONTEND=noninteractive
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
libpq-dev \
libmariadb-dev \
curl \
ca-certificates \
7zip \
tzdata \
nginx \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Clone release backend code
RUN wget "https://github.com/rommapp/romm/archive/refs/tags/${VERSION}.tar.gz" && \
tar xfz ${VERSION}.tar.gz --strip-components=1 -C /app/code romm-${VERSION}/backend romm-${VERSION}/pyproject.toml romm-${VERSION}/uv.lock && rm ${VERSION}.tar.gz
# Install RAHasher into image
COPY --from=cloudron-builder /tmp/RALibretro/bin64/RAHasher /usr/bin/RAHasher
# Install frontend into image
WORKDIR ${WEBSERVER_FOLDER}
COPY --from=frontend-build /front/dist ${WEBSERVER_FOLDER}
COPY --from=frontend-build /front/assets ${WEBSERVER_FOLDER}/assets
COPY ./decode.js /etc/nginx/js/decode.js
COPY ./nginx.conf /etc/nginx/nginx.conf
COPY ./default.conf /etc/nginx/conf.d/default.conf
COPY --from=cloudron-builder /usr/share/nginx/src/ngx_http_js_module.so /usr/lib/nginx/modules/
COPY --from=cloudron-builder /usr/share/nginx/src/ngx_http_zip_module.so /usr/lib/nginx/modules/
RUN mkdir -p assets/romm && ln -s /app/data/assets ${WEBSERVER_FOLDER}/assets/romm/assets && ln -s /app/data/resources ${WEBSERVER_FOLDER}/assets/romm/resources
# Copy emulator to web folder
COPY --from=emulator-stage /emulatorjs ${WEBSERVER_FOLDER}/assets/emulatorjs
COPY --from=emulator-stage /ruffle ${WEBSERVER_FOLDER}/assets/ruffle
WORKDIR /app/code/
# Install uv for the non-root user
COPY --from=ghcr.io/astral-sh/uv:0.7.19 /uv /uvx /usr/local/bin/
COPY --from=ghcr.io/astral-sh/uv:$UV_VERSION /uv /uvx /usr/local/bin/
# Install Python
RUN mkdir /app/code/uv && uv python install -i /app/code/uv 3.13

19
decode.js Normal file
View File

@@ -0,0 +1,19 @@
// Decode a Base64 encoded string received as a query parameter named 'value',
// and return the decoded value in the response body.
function decodeBase64(r) {
var encodedValue = r.args.value;
if (!encodedValue) {
r.return(400, "Missing 'value' query parameter");
return;
}
try {
var decodedValue = atob(encodedValue);
r.return(200, decodedValue);
} catch (e) {
r.return(400, "Invalid Base64 encoding");
}
}
export default { decodeBase64 };

73
default.conf Normal file
View File

@@ -0,0 +1,73 @@
# Helper to get scheme regardless if we are behind a proxy or not
map $http_x_forwarded_proto $forwardscheme {
default $scheme;
https https;
}
# COEP and COOP headers for cross-origin isolation, which are set only for the
# EmulatorJS player path, to enable SharedArrayBuffer support, which is needed
# for multi-threaded cores.
map $request_uri $coep_header {
default "";
~^/rom/.*/ejs$ "require-corp";
}
map $request_uri $coop_header {
default "";
~^/rom/.*/ejs$ "same-origin";
}
server {
root /app/code/frontend;
listen 3000;
listen [::]:3000;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $forwardscheme;
location / {
try_files $uri $uri/ /index.html;
proxy_redirect off;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Allow-Headers *;
add_header Cross-Origin-Embedder-Policy $coep_header;
add_header Cross-Origin-Opener-Policy $coop_header;
}
# Static files
location /assets {
try_files $uri $uri/ =404;
}
# OpenAPI for swagger and redoc
location /openapi.json {
proxy_pass http://backend_server;
}
# Backend api calls
location /api {
proxy_pass http://backend_server;
proxy_request_buffering off;
proxy_buffering off;
}
location ~ ^/(ws|netplay) {
proxy_pass http://backend_server;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Internally redirect download requests
location /library/ {
internal;
alias "/app/data/library/";
}
# Internal decoding endpoint, used to decode base64 encoded data
location /decode {
internal;
js_content decode.decodeBase64;
}
}

88
nginx.conf Normal file
View File

@@ -0,0 +1,88 @@
load_module modules/ngx_http_js_module.so;
load_module modules/ngx_http_zip_module.so;
worker_processes auto;
pid /tmp/nginx.pid;
events {
worker_connections 768;
multi_accept on;
}
http {
client_body_temp_path /tmp/client_body 1 2;
fastcgi_temp_path /tmp/fastcgi 1 2;
proxy_temp_path /tmp/proxy;
uwsgi_temp_path /tmp/uwsgi;
scgi_temp_path /tmp/scgi;
sendfile on;
client_body_buffer_size 128k;
client_max_body_size 0;
client_header_buffer_size 1k;
large_client_header_buffers 4 16k;
send_timeout 600s;
keepalive_timeout 600s;
client_body_timeout 600s;
tcp_nopush on;
tcp_nodelay on;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
js_import /etc/nginx/js/decode.js;
map $time_iso8601 $date {
~([^+]+)T $1;
}
map $time_iso8601 $time {
~T([0-9:]+)\+ $1;
}
# Map to extract the browser name (e.g., Chrome, Firefox, etc.)
map $http_user_agent $browser {
default "Unknown";
"~Chrome/" "Chrome";
"~Firefox/" "Firefox";
"~Safari/" "Safari";
"~Edge/" "Edge";
"~Opera/" "Opera";
}
# Map to extract the OS (e.g., Windows, MacOS, Linux)
map $http_user_agent $os {
default "Unknown";
"~Windows NT" "Windows";
"~Macintosh" "macOS";
"~Linux" "Linux";
"~Android" "Android";
"~iPhone" "iOS";
}
#INFO: [nginx][2023-11-14 09:20:29] 127.0.0.1 - -"GET / HTTP/1.1" 500 177 "-" "Mozilla/5.0 (X11; Linux x86_64)"rt=0.000 uct="-" uht="-" urt="-"
log_format romm_logs 'INFO: [RomM][nginx][$date $time] '
'$remote_addr | $http_x_forwarded_for | '
'$request_method $request_uri $status | $body_bytes_sent | '
'$browser $os | $request_time';
access_log /dev/stdout romm_logs;
error_log /dev/stderr;
gzip on;
gzip_proxied any;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_min_length 1024;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
upstream backend_server {
server 127.0.0.1:5000;
}
include /etc/nginx/conf.d/*.conf;
}

View File

@@ -1,12 +1,12 @@
[program:frontend]
priority=50
directory=/app/code/frontend
environment=HOME=/app/code
command=npm run dev
user=cloudron
directory=/tmp
command=/usr/sbin/nginx -g "daemon off;"
user=root
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0