mirror of
https://github.com/Quad4-Software/MeshChatX.git
synced 2026-08-18 09:49:09 -04:00
chore(docker): add hardened Dockerfile and entrypoint script for improved security and functionality
This commit is contained in:
parent
b454757533
commit
56e8197c08
3 changed files with 136 additions and 0 deletions
|
|
@ -50,9 +50,11 @@ RUN poetry config virtualenvs.create false && \
|
||||||
|
|
||||||
COPY meshchatx ./meshchatx
|
COPY meshchatx ./meshchatx
|
||||||
COPY scripts/docker-bake-lxst-filterlib-musl.py ./scripts/docker-bake-lxst-filterlib-musl.py
|
COPY scripts/docker-bake-lxst-filterlib-musl.py ./scripts/docker-bake-lxst-filterlib-musl.py
|
||||||
|
COPY scripts/patch_lxst_pyogg_ogg_ctypes.py ./scripts/patch_lxst_pyogg_ogg_ctypes.py
|
||||||
COPY --from=build-frontend /src/meshchatx/public ./meshchatx/public
|
COPY --from=build-frontend /src/meshchatx/public ./meshchatx/public
|
||||||
|
|
||||||
RUN pip install --no-cache-dir . && \
|
RUN pip install --no-cache-dir . && \
|
||||||
|
python scripts/patch_lxst_pyogg_ogg_ctypes.py && \
|
||||||
python scripts/docker-bake-lxst-filterlib-musl.py && \
|
python scripts/docker-bake-lxst-filterlib-musl.py && \
|
||||||
find /opt/venv -type d -name "tests" -exec rm -rf {} + && \
|
find /opt/venv -type d -name "tests" -exec rm -rf {} + && \
|
||||||
find /opt/venv -type d -name "test" -exec rm -rf {} + && \
|
find /opt/venv -type d -name "test" -exec rm -rf {} + && \
|
||||||
|
|
|
||||||
90
Dockerfile.hardened
Normal file
90
Dockerfile.hardened
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
# 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).
|
||||||
|
|
||||||
|
ARG NODE_IMAGE=cgr.dev/chainguard/node:latest-dev
|
||||||
|
ARG PYTHON_BUILD_IMAGE=cgr.dev/chainguard/python:latest-dev
|
||||||
|
ARG PYTHON_RUNTIME_IMAGE=cgr.dev/chainguard/python:latest-dev
|
||||||
|
|
||||||
|
FROM ${NODE_IMAGE} AS build-frontend
|
||||||
|
USER root
|
||||||
|
WORKDIR /src
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
COPY package.json pnpm-lock.yaml vite.config.js ./
|
||||||
|
COPY patches ./patches
|
||||||
|
COPY meshchatx/src/frontend ./meshchatx/src/frontend
|
||||||
|
RUN npm install -g pnpm@10.33.0 && \
|
||||||
|
pnpm config set verify-store-integrity true && \
|
||||||
|
pnpm install --frozen-lockfile && \
|
||||||
|
pnpm run build-frontend
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir --upgrade "pip>=26.0" poetry setuptools wheel "jaraco.context>=6.1.0"
|
||||||
|
|
||||||
|
RUN python -m venv /opt/venv
|
||||||
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir --upgrade "pip>=26.0" "setuptools" "jaraco.context>=6.1.0"
|
||||||
|
|
||||||
|
COPY pyproject.toml poetry.lock README.md ./
|
||||||
|
RUN poetry config virtualenvs.create false && \
|
||||||
|
poetry check --lock && \
|
||||||
|
poetry install --no-root --only main --no-interaction --no-ansi && \
|
||||||
|
rm -rf /root/.cache/pip /root/.cache/pypoetry
|
||||||
|
|
||||||
|
COPY meshchatx ./meshchatx
|
||||||
|
COPY scripts/patch_lxst_pyogg_ogg_ctypes.py ./scripts/patch_lxst_pyogg_ogg_ctypes.py
|
||||||
|
COPY --from=build-frontend /src/meshchatx/public ./meshchatx/public
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir . && \
|
||||||
|
python scripts/patch_lxst_pyogg_ogg_ctypes.py && \
|
||||||
|
find /opt/venv -type d -name "tests" -exec rm -rf {} + && \
|
||||||
|
find /opt/venv -type d -name "test" -exec rm -rf {} + && \
|
||||||
|
find /opt/venv -type d -name "__pycache__" -exec rm -rf {} + && \
|
||||||
|
python -m compileall -q /opt/venv
|
||||||
|
|
||||||
|
FROM ${PYTHON_RUNTIME_IMAGE}
|
||||||
|
|
||||||
|
ARG OCI_REVISION=""
|
||||||
|
ARG OCI_VERSION=""
|
||||||
|
ARG OCI_CREATED=""
|
||||||
|
|
||||||
|
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 --from=builder --chown=meshchat:meshchat /opt/venv /opt/venv
|
||||||
|
COPY scripts/docker_entrypoint_chainguard.py /docker-entrypoint.py
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.source="https://git.quad4.io/RNS-Things/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.authors="Quad4"
|
||||||
|
LABEL org.opencontainers.image.revision="${OCI_REVISION}"
|
||||||
|
LABEL org.opencontainers.image.version="${OCI_VERSION}"
|
||||||
|
LABEL org.opencontainers.image.created="${OCI_CREATED}"
|
||||||
|
|
||||||
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
|
||||||
|
USER meshchat
|
||||||
|
|
||||||
|
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/.meshchat", "--headless"]
|
||||||
44
scripts/docker_entrypoint_chainguard.py
Normal file
44
scripts/docker_entrypoint_chainguard.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# SPDX-License-Identifier: 0BSD
|
||||||
|
"""Entrypoint for Chainguard/minimal-style images without a POSIX shell."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pwd
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def _chown_tree(path: str, uid: int, gid: int) -> None:
|
||||||
|
try:
|
||||||
|
st = os.lstat(path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return
|
||||||
|
if not (st.st_uid == uid and st.st_gid == gid):
|
||||||
|
os.lchown(path, uid, gid)
|
||||||
|
if not os.path.isdir(path) or os.path.islink(path):
|
||||||
|
return
|
||||||
|
with os.scandir(path) as it:
|
||||||
|
for entry in it:
|
||||||
|
_chown_tree(entry.path, uid, gid)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
argv = sys.argv[1:]
|
||||||
|
if not argv:
|
||||||
|
print("docker-entrypoint: missing command", file=sys.stderr)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
if os.getuid() == 0:
|
||||||
|
pw = pwd.getpwnam("meshchat")
|
||||||
|
_chown_tree("/config", pw.pw_uid, pw.pw_gid)
|
||||||
|
os.initgroups(pw.pw_name, pw.pw_gid)
|
||||||
|
os.setgid(pw.pw_gid)
|
||||||
|
os.setuid(pw.pw_uid)
|
||||||
|
os.environ.setdefault("HOME", pw.pw_dir)
|
||||||
|
|
||||||
|
os.execvp(argv[0], argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue