Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 42 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ LABEL description='Slim Docker container featuring embedded Nut server from blaw
LABEL repo='eskwisit/nut-server'
LABEL email='mru2683@gmail.com'

ARG NUT_VERSION=3.3
ARG NUT_SHA256=3d8e8723c13385e098c9c76e2417e8e5a1ca3c51176b42a5d443ba9432dbc28b

# Prepare Nut install
RUN apt-get update && apt-get -y install \
RUN apt-get update && apt-get -y install --no-install-recommends \
ca-certificates \
gosu \
wget \
unzip \
gcc \
Expand All @@ -15,32 +20,58 @@ RUN apt-get update && apt-get -y install \
zlib1g-dev \
libjpeg-dev

# Fetch Nut codebase
RUN wget https://github.com/blawar/nut/archive/refs/tags/v3.3.zip
RUN unzip v3.3.zip -d /root
RUN mv /root/nut-3.3 /root/nut
# Fetch Nut codebase and verify its integrity
RUN wget -O nut.zip "https://github.com/blawar/nut/archive/refs/tags/v${NUT_VERSION}.zip"
RUN echo "${NUT_SHA256} nut.zip" | sha256sum -c -
RUN unzip nut.zip -d /root
RUN mv "/root/nut-${NUT_VERSION}" /root/nut

# Edit config
RUN mv /root/nut/conf/nut.default.conf /root/nut/conf/nut.conf
RUN sed -i '/scan/c "scan": ["\/titles"]' /root/nut/conf/nut.conf
# Local drive access lets any authenticated client read arbitrary paths of the
# container filesystem and disables ".." traversal checks
RUN sed -i '/enableLocalDriveAccess/c "enableLocalDriveAccess": 0,' /root/nut/conf/nut.conf
# Upstream ships a users.conf holding the default guest/guest account; the
# entrypoint generates a real one instead
RUN rm -f /root/nut/conf/users.conf
# Keep a pristine copy outside of the volumes so the entrypoint can restore it
RUN mkdir -p /opt/nut-defaults && cp /root/nut/conf/nut.conf /opt/nut-defaults/nut.conf

# Remove GUI packages
RUN sed -i '/pyqt5/d' /root/nut/requirements.txt
RUN sed -i '/qt-range-slider/d' /root/nut/requirements.txt

# Update dependencies carrying known vulnerabilities
RUN sed -i '/^requests/c requests>=2.32.3' /root/nut/requirements.txt
RUN sed -i '/^Pillow/c Pillow>=10.3.0' /root/nut/requirements.txt
RUN sed -i '/^urllib3/c urllib3>=1.26.19,<2' /root/nut/requirements.txt

# Add missing requirements
RUN echo markupsafe==2.0.1 >>/root/nut/requirements.txt

# Install project dependencies
RUN pip3 install -U pip
RUN pip3 install -r /root/nut/requirements.txt
RUN pip3 install -U --no-cache-dir pip
RUN pip3 install --no-cache-dir -r /root/nut/requirements.txt

# Drop the build toolchain and the bundled Nintendo shop client certificate,
# which holds a private key and is only used by the CDN downloader
RUN rm -f /root/nut/ShopN.pem
RUN apt-get purge -y --auto-remove wget unzip gcc libssl-dev libcurl4-openssl-dev zlib1g-dev libjpeg-dev
RUN apt-get install -y --no-install-recommends libcurl4 libjpeg62-turbo
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /root/nut.zip

# Run the server as an unprivileged user
RUN groupadd -g 1000 nut && useradd -u 1000 -g nut -M -d /root/nut nut
RUN chmod 711 /root
RUN mkdir -p /titles /root/nut/_NSPOUT && chown -R nut:nut /titles /root/nut

COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod 755 /usr/local/bin/docker-entrypoint.sh

VOLUME [ "/titles", "/root/nut/conf", "/root/nut/_NSPOUT" ]

EXPOSE 9000

# clean up
RUN rm v3.3.zip
RUN apt-get autoremove

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["python3", "/root/nut/nut.py", "--server"]
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,28 @@ Slim Docker image featuring embedded Nut server from [blawar/nut](https://github
docker pull eskwisit/nut-server

# Run image
docker run -d --name=nut-server -v /path/to/titles:/titles:rw eskwisit/nut-server
docker run -d --name=nut-server -e NUT_PASSWORD=change-me -v /path/to/titles:/titles:rw eskwisit/nut-server
```

## Authentication

Nut requires HTTP basic auth for every request. The container creates
`conf/users.conf` on first start from `NUT_USERNAME` (defaults to `nut`) and
`NUT_PASSWORD`. If `NUT_PASSWORD` is unset, a random password is generated and
printed once to the container logs:

```bash
docker logs nut-server
```

Mount the conf volume or set both variables to keep the credentials stable
across container recreations. Nut sends credentials over plain HTTP, so expose
it on a trusted network or behind a TLS reverse proxy only.

Local drive access is disabled (`"enableLocalDriveAccess": 0` in `conf/nut.conf`)
so clients can only browse the configured title paths. Enabling it lets any
authenticated client read arbitrary files from the container.

## Volumes

If you need to tune the configuration, you can mount titles, conf and NSPOUT volumes.
Expand Down
45 changes: 45 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/sh
set -eu

CONF_DIR=/root/nut/conf
USERS_CONF="${CONF_DIR}/users.conf"
NUT_CONF="${CONF_DIR}/nut.conf"

mkdir -p "${CONF_DIR}" /root/nut/_NSPOUT /titles

# A mounted, empty conf volume hides the bundled config, which makes Nut fall
# back to its defaults (scan of the whole working directory and local drive
# access enabled)
if [ ! -f "${NUT_CONF}" ]; then
cp /opt/nut-defaults/nut.conf "${NUT_CONF}"
fi

# Without a users.conf, Nut creates an implicit guest/guest account, so the
# server is effectively unauthenticated
if [ ! -f "${USERS_CONF}" ]; then
NUT_USERNAME="${NUT_USERNAME:-nut}"
if [ -z "${NUT_PASSWORD:-}" ]; then
NUT_PASSWORD="$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 24)"
echo "nut-server: no NUT_PASSWORD provided, generated credentials:"
echo "nut-server: username: ${NUT_USERNAME}"
echo "nut-server: password: ${NUT_PASSWORD}"
echo "nut-server: set NUT_USERNAME/NUT_PASSWORD or mount ${USERS_CONF} to keep credentials across recreations."
fi
umask 077
printf 'id|password|isAdmin\n%s|%s|1\n' "${NUT_USERNAME}" "${NUT_PASSWORD}" >"${USERS_CONF}"
umask 022
elif grep -qi '^guest|guest' "${USERS_CONF}"; then
echo "nut-server: WARNING ${USERS_CONF} still holds the default guest/guest account, replace it."
fi

unset NUT_PASSWORD

chmod 600 "${USERS_CONF}" 2>/dev/null || true

if [ "$(id -u)" != '0' ]; then
exec "$@"
fi

chown -R nut:nut /root/nut /titles 2>/dev/null || true

exec gosu nut "$@"