diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4152b159..be43f10d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -60,9 +60,21 @@ jobs: - variant: standard dockerfile: ./Dockerfile tag_suffix: "" + build_variant: standard + oci_description: "MeshChatX is an all-in-one Reticulum client." + oci_licenses: "MIT AND 0BSD" - variant: hardened dockerfile: ./Dockerfile.hardened tag_suffix: "-hardened" + build_variant: standard + oci_description: "MeshChatX is an all-in-one Reticulum client." + oci_licenses: "MIT AND 0BSD" + - variant: extra + dockerfile: ./Dockerfile + tag_suffix: "-extra" + build_variant: extra + oci_description: "MeshChatX all-in-one Reticulum client with i2pd and yggdrasil." + oci_licenses: "MIT AND 0BSD AND BSD-3-Clause AND LGPL-3.0-only" steps: - name: Verify action pins (GitHub API) env: @@ -218,6 +230,9 @@ jobs: OCI_REVISION=${{ github.sha }} OCI_VERSION=${{ steps.oci.outputs.version }} OCI_CREATED=${{ steps.oci.outputs.created }} + VARIANT=${{ matrix.build_variant }} + OCI_DESCRIPTION=${{ matrix.oci_description }} + OCI_LICENSES=${{ matrix.oci_licenses }} - name: Verify image has no package bloat run: | diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 5ce7690a..d9ce2fc6 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -72,7 +72,11 @@ jobs: run: sh scripts/ci/trivy-fs-scan.sh - name: Trivy Dockerfile misconfiguration - run: trivy config --exit-code 1 Dockerfile + run: | + set -euo pipefail + for f in Dockerfile Dockerfile.hardened; do + trivy config --exit-code 1 "$f" + done frontend: name: Build frontend artifact (CodeQL) diff --git a/CHANGELOG.md b/CHANGELOG.md index 745e845f..41f0739e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ All notable changes to this project will be documented in this file. - LXST telephony half-duplex mode, live duplex switching, push-to-talk (packetizer squelch), and richer in-call stats (rates plus mute state on the active call) - Optional Linux seccomp-BPF syscall denylist (libseccomp) alongside Landlock, with auto-detect and MESHCHAT_SECCOMP fallback - Bundled [RNS-over-HTTP](https://github.com/Quad4-Software/RNS-over-HTTP) HTTPInterface with Interfaces page client/server setup, auto-install into the Reticulum interface path, and httpx support (Android includes httpx with HTTP/2 as pure-Python wheels) +- Docker **extra** image variant (`-extra` suffix): same Alpine Dockerfile with `VARIANT=extra` (adds **i2pd** and **yggdrasil**), built and published beside standard and hardened +- Docker Alpine/hardened recipes moved into `scripts/docker/*.sh` so Dockerfiles stay thin (shared frontend, venv, overlay, and runtime setup) ### Changed diff --git a/Dockerfile b/Dockerfile index 817a4c21..ce678603 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,18 @@ -# Docker Build Stages: -# 1. build-frontend: Build static frontend assets using Node -# 2. builder: Install third-party deps into /opt/venv-deps, then app overlay -# 3. final image: runtime pkgs, deps layer, thin app overlay (better update pulls) -# -# LXST wheels ship glibc-tagged filterlib extensions only. On Alpine/musl, cffi -# compiles at build time; scripts/docker-bake-lxst-filterlib-musl.py copies the -# artifact to the import name LXST.filterlib so runtime does not need gcc. +# Alpine MeshChatX image. +# Stages: frontend, python builder (deps + app overlay), runtime. +# VARIANT=standard|extra (extra adds i2pd and yggdrasil). +# LXST musl filterlib bake runs in scripts/docker/prepare-venv.sh. -# ---- Global Build Args ---- ARG NODE_IMAGE=node:24-alpine ARG NODE_HASH=sha256:0340fa682d72068edf603c305bfbc10e23219fb0e40df58d9ea4d6f33a9798bf ARG PYTHON_IMAGE=python:3.14.4-alpine3.23 ARG PYTHON_HASH=sha256:dd4d2bd5b53d9b25a51da13addf2be586beebd5387e289e798e4083d94ca837a +ARG VARIANT=standard +ARG OCI_DESCRIPTION="MeshChatX is an all-in-one Reticulum client." +ARG OCI_LICENSES="MIT AND 0BSD" -# ---- STAGE 1: Frontend Build ---- FROM --platform=linux/amd64 ${NODE_IMAGE}@${NODE_HASH} AS build-frontend WORKDIR /src -# go is required to compile visualiser-wasm RUN apk add --no-cache git python3 go COPY package.json pnpm-lock.yaml pnpm-workspace.yaml vite.config.js ./ COPY patches ./patches @@ -27,40 +23,23 @@ COPY scripts/build-visualiser-wasm.mjs scripts/build-visualiser-wasm.mjs COPY scripts/sync-meshchatx-docs.js scripts/sync-meshchatx-docs.js COPY scripts/pip_rns_remotes.py scripts/pip_rns_remotes.py COPY scripts/build/fetch_reticulum_manual.py scripts/build/fetch_reticulum_manual.py +COPY scripts/docker/build-frontend.sh scripts/docker/build-frontend.sh COPY docs ./docs COPY visualiser-wasm ./visualiser-wasm COPY meshchatx/src/frontend ./meshchatx/src/frontend ENV GOCACHE=/tmp/go-cache ENV GOTMPDIR=/tmp/go-tmp -RUN mkdir -p /tmp/go-cache /tmp/go-tmp && \ - npm install -g pnpm@11.1.2 && \ - pnpm config set verify-store-integrity true && \ - pnpm install --frozen-lockfile && \ - MESHCHATX_REQUIRE_VISUALISER_WASM=1 pnpm run build-frontend && \ - test -s meshchatx/src/frontend/public/vendor/visualiser-wasm/visualiser.wasm && \ - test -s meshchatx/src/frontend/public/vendor/visualiser-wasm/wasm_exec.js && \ - test -s meshchatx/src/frontend/public/vendor/micron-parser-go/micron-parser-go.wasm && \ - test -s meshchatx/src/frontend/public/vendor/micron-parser-go/wasm_exec.js && \ - pnpm run build-docs - -# ---- STAGE 2: Python Builder ---- +RUN sh scripts/docker/build-frontend.sh FROM ${PYTHON_IMAGE}@${PYTHON_HASH} AS builder WORKDIR /build -RUN apk upgrade --no-cache && \ - apk add --no-cache gcc g++ musl-dev linux-headers python3-dev libffi-dev openssl-dev git - -# Install build tools in the system python +COPY scripts/docker/builder-apk-alpine.sh scripts/docker/builder-apk-alpine.sh +RUN sh scripts/docker/builder-apk-alpine.sh RUN pip install --no-cache-dir --upgrade "pip>=26.0" uv setuptools wheel "jaraco.context>=6.1.0" - -# Create the clean venv for our application dependencies RUN python -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" ENV UV_PROJECT_ENVIRONMENT=/opt/venv - -# Install essential runtime tools in the venv (cffi verify needs setuptools on Python 3.12+) RUN pip install --no-cache-dir --upgrade "pip>=26.0" "setuptools" "jaraco.context>=6.1.0" - COPY pyproject.toml uv.lock README.md CHANGELOG.md ./ COPY logo ./logo COPY vendor ./vendor @@ -68,62 +47,31 @@ COPY scripts/docker-bake-lxst-filterlib-musl.py ./scripts/docker-bake-lxst-filte COPY scripts/patch_lxst_pyogg_ogg_ctypes.py ./scripts/patch_lxst_pyogg_ogg_ctypes.py COPY scripts/patch_lxst_codec2_optional.py ./scripts/patch_lxst_codec2_optional.py COPY meshchatx/src/backend/lxst_pyogg_ctypes_compat.py ./meshchatx/src/backend/lxst_pyogg_ctypes_compat.py -# Third-party deps layer: stable across app-only updates when uv.lock is unchanged. -# --inexact keeps setuptools/jaraco.context already in the venv (cffi bake needs them). -RUN uv sync --no-group dev --no-install-project --inexact && \ - rm -rf /root/.cache/pip /root/.cache/uv && \ - pip install --no-cache-dir --upgrade "setuptools" "jaraco.context>=6.1.0" && \ - python scripts/patch_lxst_pyogg_ogg_ctypes.py && \ - python scripts/patch_lxst_codec2_optional.py && \ - python scripts/docker-bake-lxst-filterlib-musl.py && \ - rm -rf /opt/venv/lib/python*/site-packages/LXST/Platforms/android && \ - find /opt/venv -type d \( -name tests -o -name test -o -name __pycache__ \) -prune -exec rm -rf {} + && \ - python -m compileall -q /opt/venv/lib/python3.14/site-packages && \ - cp -a /opt/venv /opt/venv-deps - +COPY scripts/docker/prepare-venv.sh scripts/docker/prepare-venv.sh +COPY scripts/docker/build-app-overlay.sh scripts/docker/build-app-overlay.sh +RUN sh scripts/docker/prepare-venv.sh COPY meshchatx ./meshchatx COPY --from=build-frontend /src/meshchatx/public ./meshchatx/public +RUN sh scripts/docker/build-app-overlay.sh -# App overlay: meshchatx + vendored packages + console scripts (changes often). -RUN pip install --no-cache-dir . && \ - PYVER="$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')" && \ - SP="/opt/venv/lib/python${PYVER}/site-packages" && \ - OUT="/opt/app-overlay" && \ - mkdir -p "${OUT}/lib/python${PYVER}/site-packages" "${OUT}/bin" && \ - for pkg in meshchatx lxmfy rns_filesync; do \ - cp -a "${SP}/${pkg}" "${OUT}/lib/python${PYVER}/site-packages/"; \ - done && \ - cp -a "${SP}"/reticulum_meshchatx*.dist-info "${OUT}/lib/python${PYVER}/site-packages/" && \ - for cmd in meshchat meshchatx meshchatx-repository-http lxmfy rns-filesync; do \ - cp -a "/opt/venv/bin/${cmd}" "${OUT}/bin/"; \ - done && \ - find "${OUT}" -type d -name __pycache__ -prune -exec rm -rf {} + && \ - python -m compileall -q "${OUT}/lib/python${PYVER}/site-packages" - -# ---- STAGE 3: Final Image ---- -# Layer order matters for update pulls: base runtime, third-party venv, thin app overlay. FROM ${PYTHON_IMAGE}@${PYTHON_HASH} - -RUN apk upgrade --no-cache && \ - apk add --no-cache opusfile libffi espeak-ng su-exec && \ - python -m pip install --no-cache-dir --upgrade "pip>=26.0" "setuptools" "jaraco.context>=6.1.0" && \ - rm -rf /root/.cache/pip && \ - addgroup -g 1000 meshchat && adduser -u 1000 -G meshchat -S meshchat && \ - mkdir -p /config && chown meshchat:meshchat /config - +ARG VARIANT +ARG OCI_DESCRIPTION +ARG OCI_LICENSES +COPY scripts/docker/runtime-setup.sh /tmp/runtime-setup.sh +RUN sh /tmp/runtime-setup.sh "${VARIANT}" && rm /tmp/runtime-setup.sh COPY --from=builder --chown=meshchat:meshchat /opt/venv-deps /opt/venv COPY --from=builder --chown=meshchat:meshchat /opt/app-overlay/ /opt/venv/ COPY scripts/docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh -# Declare after COPY so per-build OCI values do not invalidate runtime or venv layers. ARG OCI_REVISION="" ARG OCI_VERSION="" ARG OCI_CREATED="" LABEL org.opencontainers.image.source="https://github.com/Quad4-Software/MeshChatX" -LABEL org.opencontainers.image.description="MeshChatX is a all in one Reticulum client." -LABEL org.opencontainers.image.licenses="MIT AND 0BSD" +LABEL org.opencontainers.image.description="${OCI_DESCRIPTION}" +LABEL org.opencontainers.image.licenses="${OCI_LICENSES}" LABEL org.opencontainers.image.authors="Quad4" LABEL org.opencontainers.image.revision="${OCI_REVISION}" LABEL org.opencontainers.image.version="${OCI_VERSION}" @@ -132,12 +80,11 @@ LABEL org.opencontainers.image.created="${OCI_CREATED}" ENV PATH="/opt/venv/bin:$PATH" ENV PYTHONUNBUFFERED=1 ENV PYTHONDONTWRITEBYTECODE=1 -# No PulseAudio in the image: LXST LineSource/LineSink cannot open host devices. ENV MESHCHAT_FORCE_WEB_AUDIO=1 USER meshchat -# Note: Podman defaults to OCI image layout, which drops HEALTHCHECK; use: podman build --format docker +# Podman OCI layout drops HEALTHCHECK. Use: podman build --format docker HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \ CMD ["python", "-c", "import ssl, urllib.request; urllib.request.urlopen('https://127.0.0.1:8000/api/v1/status', context=ssl._create_unverified_context())"] diff --git a/Dockerfile.hardened b/Dockerfile.hardened index c319f6cc..b3ef194a 100644 --- a/Dockerfile.hardened +++ b/Dockerfile.hardened @@ -1,10 +1,7 @@ # syntax=docker/dockerfile:1 -# Multi-stage image on Wolfi (Chainguard): Node dev for frontend, Python dev for -# Poetry/cffi builds, Python dev for runtime so native libs can be installed with -# apk (the minimal chainguard/python image has no shell or package manager). -# Wolfi provides the opus package (libopus); Alpine's opusfile split is not present. -# Glibc runtime uses LXST wheels as published; the musl filterlib bake is not used. -# Voicemail greeting synthesis is optional without espeak-ng (see voicemail_manager). +# Chainguard/Wolfi MeshChatX image (hardened tags). +# Glibc LXST wheels as published. Musl filterlib bake is skipped. +# Runtime uses opus (not Alpine opusfile). Voicemail without espeak-ng is optional. ARG NODE_IMAGE=cgr.dev/chainguard/node:latest-dev ARG PYTHON_BUILD_IMAGE=cgr.dev/chainguard/python:latest-dev @@ -13,7 +10,6 @@ ARG PYTHON_RUNTIME_IMAGE=cgr.dev/chainguard/python:latest-dev FROM --platform=linux/amd64 ${NODE_IMAGE} AS build-frontend USER root WORKDIR /src -# go is required to compile visualiser-wasm (gitignored binary; not in image context) RUN apk add --no-cache git python3 go COPY package.json pnpm-lock.yaml pnpm-workspace.yaml vite.config.js ./ COPY patches ./patches @@ -24,94 +20,52 @@ COPY scripts/build-visualiser-wasm.mjs scripts/build-visualiser-wasm.mjs COPY scripts/sync-meshchatx-docs.js scripts/sync-meshchatx-docs.js COPY scripts/pip_rns_remotes.py scripts/pip_rns_remotes.py COPY scripts/build/fetch_reticulum_manual.py scripts/build/fetch_reticulum_manual.py +COPY scripts/docker/build-frontend.sh scripts/docker/build-frontend.sh COPY docs ./docs COPY visualiser-wasm ./visualiser-wasm COPY meshchatx/src/frontend ./meshchatx/src/frontend ENV GOCACHE=/tmp/go-cache ENV GOTMPDIR=/tmp/go-tmp -RUN mkdir -p /tmp/go-cache /tmp/go-tmp && \ - npm install -g pnpm@11.1.2 && \ - pnpm config set verify-store-integrity true && \ - pnpm install --frozen-lockfile && \ - MESHCHATX_REQUIRE_VISUALISER_WASM=1 pnpm run build-frontend && \ - test -s meshchatx/src/frontend/public/vendor/visualiser-wasm/visualiser.wasm && \ - test -s meshchatx/src/frontend/public/vendor/visualiser-wasm/wasm_exec.js && \ - test -s meshchatx/src/frontend/public/vendor/micron-parser-go/micron-parser-go.wasm && \ - test -s meshchatx/src/frontend/public/vendor/micron-parser-go/wasm_exec.js && \ - pnpm run build-docs +RUN sh scripts/docker/build-frontend.sh FROM ${PYTHON_BUILD_IMAGE} AS builder USER root WORKDIR /build -RUN apk add --no-cache build-base git pkgconf openssl-dev libffi-dev linux-headers - +COPY scripts/docker/builder-apk-chainguard.sh scripts/docker/builder-apk-chainguard.sh +RUN sh scripts/docker/builder-apk-chainguard.sh RUN pip install --no-cache-dir --upgrade "pip>=26.0" uv setuptools wheel "jaraco.context>=6.1.0" - RUN python -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" ENV UV_PROJECT_ENVIRONMENT=/opt/venv - RUN pip install --no-cache-dir --upgrade "pip>=26.0" "setuptools" "jaraco.context>=6.1.0" - COPY pyproject.toml uv.lock README.md CHANGELOG.md ./ COPY logo ./logo COPY vendor ./vendor COPY scripts/patch_lxst_pyogg_ogg_ctypes.py ./scripts/patch_lxst_pyogg_ogg_ctypes.py COPY scripts/patch_lxst_codec2_optional.py ./scripts/patch_lxst_codec2_optional.py COPY meshchatx/src/backend/lxst_pyogg_ctypes_compat.py ./meshchatx/src/backend/lxst_pyogg_ctypes_compat.py -# Third-party deps layer: stable across app-only updates when uv.lock is unchanged. -# --inexact keeps setuptools/jaraco.context already in the venv (cffi needs them). -RUN uv sync --no-group dev --no-install-project --inexact && \ - rm -rf /root/.cache/pip /root/.cache/uv && \ - pip install --no-cache-dir --upgrade "setuptools" "jaraco.context>=6.1.0" && \ - python scripts/patch_lxst_pyogg_ogg_ctypes.py && \ - python scripts/patch_lxst_codec2_optional.py && \ - rm -rf /opt/venv/lib/python*/site-packages/LXST/Platforms/android && \ - find /opt/venv -type d \( -name tests -o -name test -o -name __pycache__ \) -prune -exec rm -rf {} + && \ - python -m compileall -q /opt/venv && \ - cp -a /opt/venv /opt/venv-deps - +COPY scripts/docker/prepare-venv.sh scripts/docker/prepare-venv.sh +COPY scripts/docker/build-app-overlay.sh scripts/docker/build-app-overlay.sh +ENV MESHCHATX_DOCKER_SKIP_MUSL_FILTERLIB=1 +RUN sh scripts/docker/prepare-venv.sh COPY meshchatx ./meshchatx COPY --from=build-frontend /src/meshchatx/public ./meshchatx/public - -# App overlay: meshchatx + vendored packages + console scripts (changes often). -RUN pip install --no-cache-dir . && \ - PYVER="$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')" && \ - SP="/opt/venv/lib/python${PYVER}/site-packages" && \ - OUT="/opt/app-overlay" && \ - mkdir -p "${OUT}/lib/python${PYVER}/site-packages" "${OUT}/bin" && \ - for pkg in meshchatx lxmfy rns_filesync; do \ - cp -a "${SP}/${pkg}" "${OUT}/lib/python${PYVER}/site-packages/"; \ - done && \ - cp -a "${SP}"/reticulum_meshchatx*.dist-info "${OUT}/lib/python${PYVER}/site-packages/" && \ - for cmd in meshchat meshchatx meshchatx-repository-http lxmfy rns-filesync; do \ - cp -a "/opt/venv/bin/${cmd}" "${OUT}/bin/"; \ - done && \ - find "${OUT}" -type d -name __pycache__ -prune -exec rm -rf {} + && \ - python -m compileall -q "${OUT}/lib/python${PYVER}/site-packages" +RUN sh scripts/docker/build-app-overlay.sh FROM ${PYTHON_RUNTIME_IMAGE} - USER root -RUN apk add --no-cache opus libffi shadow && \ - pip install --no-cache-dir --upgrade "pip>=26.0" "setuptools" "jaraco.context>=6.1.0" && \ - rm -rf /root/.cache/pip && \ - groupadd -g 1000 meshchat && \ - useradd --uid 1000 --gid 1000 --create-home --home-dir /home/meshchat \ - --shell /sbin/nologin meshchat && \ - mkdir -p /config && chown meshchat:meshchat /config - +COPY scripts/docker/runtime-setup-chainguard.sh /tmp/runtime-setup-chainguard.sh +RUN sh /tmp/runtime-setup-chainguard.sh && rm /tmp/runtime-setup-chainguard.sh COPY --from=builder --chown=meshchat:meshchat /opt/venv-deps /opt/venv COPY --from=builder --chown=meshchat:meshchat /opt/app-overlay/ /opt/venv/ COPY scripts/docker_entrypoint_chainguard.py /docker-entrypoint.py -# Declare after COPY so per-build OCI values do not invalidate runtime or venv layers. ARG OCI_REVISION="" ARG OCI_VERSION="" ARG OCI_CREATED="" LABEL org.opencontainers.image.source="https://github.com/Quad4-Software/MeshChatX" -LABEL org.opencontainers.image.description="MeshChatX is a all in one Reticulum client." +LABEL org.opencontainers.image.description="MeshChatX is an all-in-one Reticulum client." LABEL org.opencontainers.image.licenses="MIT AND 0BSD" LABEL org.opencontainers.image.authors="Quad4" LABEL org.opencontainers.image.revision="${OCI_REVISION}" @@ -129,4 +83,4 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \ CMD ["python", "-c", "import ssl, urllib.request; urllib.request.urlopen('https://127.0.0.1:8000/api/v1/status', context=ssl._create_unverified_context())"] ENTRYPOINT ["/usr/bin/python", "/docker-entrypoint.py"] -CMD ["/opt/venv/bin/meshchatx", "--host=0.0.0.0", "--reticulum-config-dir=/config/.reticulum", "--storage-dir=/config/.reticulum-meshchatx", "--headless"] \ No newline at end of file +CMD ["/opt/venv/bin/meshchatx", "--host=0.0.0.0", "--reticulum-config-dir=/config/.reticulum", "--storage-dir=/config/.reticulum-meshchatx", "--headless"] diff --git a/README.md b/README.md index 4b8b9b72..8524a404 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Notes: - **Docker Hub:** `quad4io/meshchatx` - **GHCR:** `ghcr.io/quad4-software/meshchatx` +- **Variants:** default tags (for example `:latest`) are the standard Alpine image. Use `-hardened` for the Chainguard/Wolfi image, or `-extra` for Alpine plus **i2pd** and **yggdrasil** (for example `:latest-extra`, built from the same `Dockerfile` with `VARIANT=extra`). ```bash docker compose up -d diff --git a/docs/en/architecture.md b/docs/en/architecture.md index cf182e8f..127c3e8c 100644 --- a/docs/en/architecture.md +++ b/docs/en/architecture.md @@ -107,7 +107,7 @@ One source tree produces: - Development runs via `uv run python -m meshchatx.meshchat` - Python wheels with bundled `public/` assets -- Container images (Dockerfile and hardened variants) +- Container images (Alpine Dockerfile with standard/extra VARIANT, plus hardened Chainguard) - Electron builds for Windows, macOS, and Linux - Android APK via Chaquopy diff --git a/docs/en/installation.md b/docs/en/installation.md index d5f2b944..db87df23 100644 --- a/docs/en/installation.md +++ b/docs/en/installation.md @@ -26,7 +26,7 @@ MeshChatX can be installed in several ways. All release artifacts that ship the | Android APK | Yes | Phones, tablets, Meta Quest sideload | | From source | Built locally | Development and custom builds | -Release images are published to Docker Hub (`quad4io/meshchatx`) and GHCR (`ghcr.io/quad4-software/meshchatx`). +Release images are published to Docker Hub (`quad4io/meshchatx`) and GHCR (`ghcr.io/quad4-software/meshchatx`). Tag suffixes: none for the standard Alpine image, `-hardened` for Chainguard/Wolfi, `-extra` for Alpine plus i2pd and yggdrasil (`VARIANT=extra` on the same Dockerfile). ## Docker diff --git a/meshchatx.rsm b/meshchatx.rsm index 9f3ea792..523462e5 100644 Binary files a/meshchatx.rsm and b/meshchatx.rsm differ diff --git a/meshchatx/src/frontend/components/TutorialPrivacyStep.vue b/meshchatx/src/frontend/components/TutorialPrivacyStep.vue index 19252497..e85f5690 100644 --- a/meshchatx/src/frontend/components/TutorialPrivacyStep.vue +++ b/meshchatx/src/frontend/components/TutorialPrivacyStep.vue @@ -247,6 +247,7 @@ export default {