diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e88c39ce9..c3b497f74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,22 +42,22 @@ jobs: run: echo "Triggered by ${{ github.event_name }}" - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v4.3.0 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v3.6.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v3.11.1 - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v3.5.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v3.5.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -65,7 +65,7 @@ jobs: - name: Generate Docker metadata (slim) id: meta-slim - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v5.8.0 with: images: | name=rommapp/romm @@ -85,7 +85,7 @@ jobs: - name: Generate Docker metadata (full) id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v5.8.0 with: images: | name=rommapp/romm @@ -106,7 +106,7 @@ jobs: - name: Build slim image id: build-slim - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v6.18.0 with: file: docker/Dockerfile context: . @@ -118,7 +118,7 @@ jobs: - name: Build full image id: build-full - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v6.18.0 with: file: docker/Dockerfile context: . diff --git a/.github/workflows/i18n.yml b/.github/workflows/i18n.yml index f513c62d0..de0a94b82 100644 --- a/.github/workflows/i18n.yml +++ b/.github/workflows/i18n.yml @@ -13,10 +13,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v4.3.0 - name: Set up Python 3.13 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6.0.0 with: python-version: "3.13" diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 51c857d23..ba998fdde 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -31,7 +31,7 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v4.3.0 - name: Install mariadb connectors run: | @@ -39,7 +39,7 @@ jobs: sudo apt-get install -y libmariadb3 libmariadb-dev - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@v6.7.0 - name: Install python run: | @@ -62,7 +62,7 @@ jobs: uv run pytest -vv --maxfail=10 --junitxml=pytest-report.xml --cov --cov-report xml:coverage.xml --cov-config=.coveragerc . - name: Publish test results - uses: EnricoMi/publish-unit-test-result-action/linux@v2 + uses: EnricoMi/publish-unit-test-result-action/linux@sha-3a74b29 if: (!cancelled()) with: files: | diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index f1714fcdc..089585b82 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -26,26 +26,26 @@ jobs: run: echo "Triggered by ${{ github.event_name }}" - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v4.3.0 with: ref: ${{ github.event.inputs.branch }} fetch-depth: 0 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v3.6.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v3.11.1 - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v3.5.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Generate Docker metadata id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v5.8.0 with: images: | name=rommapp/romm-testing @@ -54,7 +54,7 @@ jobs: - name: Build full image id: build-full - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v6.18.0 with: file: docker/Dockerfile context: . diff --git a/.github/workflows/trunk-check.yml b/.github/workflows/trunk-check.yml index 033b4bea5..e5c4f8dab 100644 --- a/.github/workflows/trunk-check.yml +++ b/.github/workflows/trunk-check.yml @@ -17,6 +17,6 @@ jobs: contents: read # For repo checkout steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v4.3.0 - name: Trunk Check - uses: trunk-io/trunk-action@v1 + uses: trunk-io/trunk-action@v1.2.4 diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 4d6bf4e5b..a9a826ce0 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -20,10 +20,10 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v4.3.0 - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5.0.0 with: node-version: "18" diff --git a/.trunk/setup-ci/action.yaml b/.trunk/setup-ci/action.yaml index b0b37da43..0021cf330 100644 --- a/.trunk/setup-ci/action.yaml +++ b/.trunk/setup-ci/action.yaml @@ -5,7 +5,7 @@ runs: using: composite steps: - name: Setup node - uses: actions/setup-node@v4 + uses: actions/setup-node@v5.0.0 with: node-version: 18 diff --git a/docker/Dockerfile b/docker/Dockerfile index c420ca4c0..86a109a26 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,26 +11,39 @@ # - dev-slim: Slim image with development dependencies # - dev-full: Full image with emulator stage and development dependencies -# Versions: + +# ARGUMENT DECLARATIONS ARG ALPINE_VERSION=3.22 -ARG NGINX_VERSION=1.29.0 -ARG NODE_VERSION=20.19 +ARG ALPINE_SHA256=4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 ARG PYTHON_VERSION=3.13 - -# Alias stages: -FROM python:${PYTHON_VERSION}-alpine${ALPINE_VERSION} AS python-alias +ARG PYTHON_ALPINE_SHA256=9ba6d8cbebf0fb6546ae71f2a1c14f6ffd2fdab83af7fa5669734ef30ad48844 +ARG NODE_VERSION=20.19 +ARG NODE_ALPINE_SHA256=eabac870db94f7342d6c33560d6613f188bbcf4bbe1f4eb47d5e2a08e1a37722 +ARG NGINX_VERSION=1.29.1 +ARG NGINX_SHA256=42a516af16b852e33b7682d5ef8acbd5d13fe08fecadc7ed98605ba5e3b26ab8 +ARG UV_VERSION=0.7.19 +ARG UV_SHA256=9ce16aa2fe33496c439996865dc121371bb33fd5fb37500007d48e2078686b0d -FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS frontend-build +FROM python:${PYTHON_VERSION}-alpine${ALPINE_VERSION}@sha256:${PYTHON_ALPINE_SHA256} AS python-alias + + +# FRONTEND BUILD +FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION}@sha256:${NODE_ALPINE_SHA256} AS frontend-build WORKDIR /front COPY ./frontend/package*.json ./ -RUN npm ci +RUN npm ci --ignore-scripts --no-audit --no-fund COPY ./frontend ./ RUN npm run build +# https://github.com/astral-sh/uv/pkgs/container/uv/452595714 +FROM ghcr.io/astral-sh/uv:${UV_VERSION}-python${PYTHON_VERSION}-alpine@sha256:${UV_SHA256} AS uv-stage + + +# BACKEND PYTHON BUILD FROM python-alias AS backend-build # git is needed to install streaming-form-data fork @@ -43,14 +56,13 @@ RUN apk add --no-cache \ mariadb-connector-c-dev \ musl-dev -COPY --from=ghcr.io/astral-sh/uv:0.7.19 /uv /uvx /bin/ +COPY --from=uv-stage /usr/local/bin/uv /usr/local/bin/uvx /bin/ WORKDIR /src COPY ./pyproject.toml ./uv.lock /src/ RUN uv sync --locked --no-cache - FROM backend-build AS backend-dev-build # linux-headers is needed to install psutil @@ -60,8 +72,8 @@ RUN apk add --no-cache \ RUN uv sync --locked --no-cache --all-extras -FROM alpine:${ALPINE_VERSION} AS rahasher-build - +# CUSTOM RAHASHER FOR RETROACHIEVEMENTS +FROM alpine:${ALPINE_VERSION}@sha256:${ALPINE_SHA256} AS rahasher-build RUN apk add --no-cache \ g++ \ git \ @@ -84,24 +96,34 @@ RUN git clone --recursive --branch "${RALIBRETRO_VERSION}" --depth 1 https://git make HAVE_CHD=1 -f ./Makefile.RAHasher -FROM alpine:${ALPINE_VERSION} AS emulator-stage +# FETCH EMULATORJS AND RUFFLE +FROM alpine:${ALPINE_VERSION}@sha256:${ALPINE_SHA256} AS emulator-stage RUN apk add --no-cache \ 7zip \ - wget + 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 -rf "${EMULATORJS_VERSION}.7z"; + rm -f "${EMULATORJS_VERSION}.7z" ARG RUFFLE_VERSION=nightly-2025-08-14 ARG RUFFLE_FILE=ruffle-nightly-2025_08_14-web-selfhosted.zip -RUN wget "https://github.com/ruffle-rs/ruffle/releases/download/${RUFFLE_VERSION}/${RUFFLE_FILE}" && \ - unzip -o "${RUFFLE_FILE}" -d /ruffle && \ - rm -f "${RUFFLE_FILE}"; +ARG RUFFLE_SHA256=178870c5e7dd825a8df35920dfc5328d83e53f3c4d5d95f70b1ea9cd13494151 -FROM alpine:${ALPINE_VERSION} AS nginx-build +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}" + + +# BUILD NGINX MODULE WITH MOD_ZIP +FROM alpine:${ALPINE_VERSION}@sha256:${ALPINE_SHA256} AS nginx-build RUN apk add --no-cache \ gcc \ @@ -115,14 +137,14 @@ ARG NGINX_VERSION # The specified commit SHA is the latest commit on the `master` branch at the time of writing. # It includes a fix to correctly calculate CRC-32 checksums when using upstream subrequests. # TODO: Move to a tagged release of `mod_zip`, once a version newer than 1.3.0 is released. -ARG NGINX_MOD_ZIP_SHA=a9f9afa441117831cc712a832c98408b3f0416f6 +ARG NGINX_MOD_ZIP_COMMIT=a9f9afa441117831cc712a832c98408b3f0416f6 -# Clone both nginx and `ngx_http_zip_module` repositories, needed to compile the module from source. +# Clone both `nginx` and `ngx_http_zip_module` repositories, needed to compile the module from source. # This is needed to be able to dinamically load it as a module in the final image. `nginx` Docker # images do not have a simple way to include third-party modules. RUN git clone https://github.com/evanmiller/mod_zip.git && \ cd ./mod_zip && \ - git checkout "${NGINX_MOD_ZIP_SHA}" && \ + git checkout "${NGINX_MOD_ZIP_COMMIT}" && \ cd ../ && \ git clone --branch "release-${NGINX_VERSION}" --depth 1 https://github.com/nginx/nginx.git && \ cd ./nginx && \ @@ -131,10 +153,10 @@ RUN git clone https://github.com/evanmiller/mod_zip.git && \ chmod 644 ./objs/ngx_http_zip_module.so -FROM nginx:${NGINX_VERSION}-alpine${ALPINE_VERSION} AS production-stage +# PRODUCTION STAGE +FROM nginx:${NGINX_VERSION}-alpine${ALPINE_VERSION}@sha256:${NGINX_SHA256} AS production-stage ARG WEBSERVER_FOLDER=/var/www/html -# Install required packages and dependencies RUN apk add --no-cache \ bash \ libmagic \ @@ -175,13 +197,15 @@ COPY ./docker/gunicorn/logging.conf /etc/gunicorn/logging.conf # User permissions # - Create default user `romm` (1000) and group `romm` (1000). # - Create base directories and make default user/group the owner. -# - Make nginx configuration files writable by everyone, for `envsubst` to work -# when a custom UID/GID is used. +# - Make nginx configuration files writable by everyone for `envsubst` to work RUN addgroup -g 1000 -S romm && adduser -u 1000 -D -S -G romm romm && \ - mkdir /romm /redis-data && chown romm:romm /romm /redis-data && \ + mkdir /romm /redis-data && \ + chown romm:romm /romm /redis-data && \ + chmod 755 /romm /redis-data && \ chmod -R a+w /etc/nginx/conf.d +# SLIM IMAGE FROM scratch AS slim-image COPY --from=production-stage / / @@ -190,10 +214,15 @@ COPY --from=backend-build /src/.venv /src/.venv ENV PATH="/src/.venv/bin:${PATH}" +# Security: Set security-focused environment variables +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV PYTHONPATH=/backend + # Declare the supported volumes VOLUME ["/romm/resources", "/romm/library", "/romm/assets", "/romm/config", "/redis-data"] -# Expose ports and start +# Expose non-privileged ports EXPOSE 8080 6379/tcp WORKDIR /romm @@ -201,6 +230,7 @@ ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["/init"] +# FULL IMAGE FROM slim-image AS full-image ARG WEBSERVER_FOLDER=/var/www/html COPY --from=emulator-stage /emulatorjs ${WEBSERVER_FOLDER}/assets/emulatorjs diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c9f2f7855..c832ac893 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,6 +23,7 @@ "qrcode": "^1.5.4", "semver": "^7.6.2", "socket.io-client": "^4.7.5", + "tailwindcss": "^4.0.0", "vanilla-tilt": "^1.8.1", "vue": "^3.4.27", "vue-i18n": "^11.1.10", @@ -44,7 +45,6 @@ "eslint-plugin-vue": "^9.33.0", "globals": "^16.0.0", "openapi-typescript-codegen": "^0.29.0", - "tailwindcss": "^4.0.0", "typescript": "^5.7.3", "typescript-eslint": "^8.42.0", "vite": "^6.3.6", @@ -8374,7 +8374,6 @@ "version": "4.1.12", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", - "dev": true, "license": "MIT" }, "node_modules/tapable": { diff --git a/frontend/package.json b/frontend/package.json index 8e3b2defe..772b1bf15 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,7 +21,7 @@ "type": "module", "scripts": { "dev": "vite --host", - "build": "npm run typecheck && vite build", + "build": "vite build", "preview": "vite preview", "typecheck": "vue-tsc --noEmit", "generate": "openapi --input http://127.0.0.1:3000/openapi.json --output ./src/__generated__ --client axios --useOptions --useUnionTypes --exportServices false --exportSchemas false --exportCore false", @@ -42,6 +42,7 @@ "qrcode": "^1.5.4", "semver": "^7.6.2", "socket.io-client": "^4.7.5", + "tailwindcss": "^4.0.0", "vanilla-tilt": "^1.8.1", "vue": "^3.4.27", "vue-i18n": "^11.1.10", @@ -63,7 +64,6 @@ "eslint-plugin-vue": "^9.33.0", "globals": "^16.0.0", "openapi-typescript-codegen": "^0.29.0", - "tailwindcss": "^4.0.0", "typescript": "^5.7.3", "typescript-eslint": "^8.42.0", "vite": "^6.3.6",