diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f1a6606 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,59 @@ +# Git and version control +.git +.gitignore +.gitattributes + +# Documentation +README.md +*.md +docs/ + +# CI/CD +.github/ +.gitlab-ci.yml +.travis.yml + +# Docker files +Dockerfile* +docker-compose* +.dockerignore + +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Logs +*.log +logs/ + +# Temporary files +tmp/ +temp/ +.tmp + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Testing +coverage/ +.nyc_output/ +test-results/ + +# Build artifacts +dist/ +build/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 47d5384..ae8e58d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,69 +1,106 @@ +# Build stage - Download and prepare webapp FROM alpine:3.22.0 AS build -# set version label +# Set version label ARG WEBAPP_VERSION -RUN apk add --no-cache \ +# Install build dependencies with virtual package for easy cleanup +RUN apk add --no-cache --virtual .build-deps \ bash \ - busybox \ curl \ git \ jq \ - npm && \ - mkdir /app && \ - if [ -z ${WEBAPP_VERSION+x} ]; then \ + npm \ + && mkdir /app \ + # Determine webapp version if not provided + && if [ -z "${WEBAPP_VERSION+x}" ]; then \ WEBAPP_VERSION=$(curl -sX GET "https://api.github.com/repos/netbootxyz/webapp/releases/latest" \ | awk '/tag_name/{print $4;exit}' FS='[""]'); \ - fi && \ - curl -o /tmp/webapp.tar.gz -L \ - "https://github.com/netbootxyz/webapp/archive/${WEBAPP_VERSION}.tar.gz" && \ - tar xf /tmp/webapp.tar.gz -C /app/ --strip-components=1 && \ - npm install --prefix /app && \ - rm -rf /tmp/* + fi \ + # Download and extract webapp + && curl -o /tmp/webapp.tar.gz -L \ + "https://github.com/netbootxyz/webapp/archive/${WEBAPP_VERSION}.tar.gz" \ + && tar xf /tmp/webapp.tar.gz -C /app/ --strip-components=1 \ + # Install only production dependencies + && cd /app \ + && npm install --omit=dev --no-audit --no-fund \ + # Clean up build artifacts and cache + && npm cache clean --force \ + && rm -rf /tmp/* \ + && apk del .build-deps +# Production stage - Final container FROM alpine:3.22.0 -# set version label +# Build arguments for labels ARG BUILD_DATE ARG VERSION +ARG VCS_REF -LABEL build_version="netboot.xyz version: ${VERSION} Build-date: ${BUILD_DATE}" -LABEL maintainer="antonym" -LABEL org.opencontainers.image.description="netboot.xyz official docker container - Your favorite operating systems in one place. A network-based bootable operating system installer based on iPXE." +# Enhanced container labels following OCI spec +LABEL org.opencontainers.image.title="netboot.xyz" \ + org.opencontainers.image.description="Your favorite operating systems in one place. A network-based bootable operating system installer based on iPXE." \ + org.opencontainers.image.version="${VERSION}" \ + org.opencontainers.image.created="${BUILD_DATE}" \ + org.opencontainers.image.revision="${VCS_REF}" \ + org.opencontainers.image.vendor="netboot.xyz" \ + org.opencontainers.image.url="https://netboot.xyz" \ + org.opencontainers.image.source="https://github.com/netbootxyz/docker-netbootxyz" \ + org.opencontainers.image.licenses="Apache-2.0" \ + maintainer="antonym" +# Install runtime dependencies and configure system in a single layer RUN apk add --no-cache \ + # Core utilities bash \ busybox \ curl \ - dnsmasq \ envsubst \ - git \ jq \ - nghttp2-dev \ + tar \ + # Network services + dnsmasq \ nginx \ nodejs \ + # System services shadow \ sudo \ supervisor \ syslog-ng \ - tar && \ - groupmod -g 1000 users && \ - useradd -u 911 -U -d /config -s /bin/false nbxyz && \ - usermod -G users nbxyz && \ - mkdir /app /config /defaults + # Security tools + gosu \ + # Runtime libraries + nghttp2-dev \ + # Create required directories + && mkdir -p /app /config /defaults \ + # Remove unnecessary packages to reduce size + && rm -rf /var/cache/apk/* +# Copy webapp from build stage COPY --from=build /app /app -ENV TFTPD_OPTS='' -ENV NGINX_PORT='80' -ENV WEB_APP_PORT='3000' +# Environment variables with defaults +ENV TFTPD_OPTS='' \ + NGINX_PORT='80' \ + WEB_APP_PORT='3000' \ + NODE_ENV='production' \ + NPM_CONFIG_CACHE='/tmp/.npm' \ + PUID='1000' \ + PGID='1000' EXPOSE 69/udp EXPOSE 80 EXPOSE 3000 -COPY root/ / +# Copy configuration files and scripts +COPY --chown=root:root root/ / + +# Make scripts executable +RUN chmod +x /start.sh /init.sh /healthcheck.sh /usr/local/bin/dnsmasq-wrapper.sh -HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 CMD /healthcheck.sh +# Enhanced health check with better timing for slow systems +HEALTHCHECK --interval=30s --timeout=15s --start-period=60s --retries=3 \ + CMD /healthcheck.sh -CMD ["sh","/start.sh"] +# Use exec form for better signal handling +CMD ["/start.sh"] diff --git a/README.md b/README.md index 7000923..78b03e2 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ The following snippets are examples of starting up the container. ```shell docker run -d \ --name=netbootxyz \ + -e PUID=1000 `# optional, UserID for volume permissions` \ + -e PGID=1000 `# optional, GroupID for volume permissions` \ -e MENU_VERSION=2.0.84 `# optional` \ -e NGINX_PORT=80 `# optional` \ -e WEB_APP_PORT=3000 `# optional` \ @@ -114,6 +116,8 @@ Container images are configured using parameters passed at runtime (such as thos | `-p 3000` | Web configuration interface. | | `-p 69/udp` | TFTP Port. | | `-p 80` | NGINX server for hosting assets. | +| `-e PUID=1000` | UserID for volume permissions - see below for explanation | +| `-e PGID=1000` | GroupID for volume permissions - see below for explanation | | `-e WEB_APP_PORT=3000` | Specify a different port for the web configuration interface to listen on. | | `-e NGINX_PORT=80` | Specify a different port for NGINX service to listen on. | | `-e MENU_VERSION=2.0.76` | Specify a specific version of boot files you want to use from netboot.xyz (unset pulls latest) | @@ -121,6 +125,23 @@ Container images are configured using parameters passed at runtime (such as thos | `-v /config` | Storage for boot menu files and web application config | | `-v /assets` | Storage for netboot.xyz bootable assets (live CDs and other files) | +## User / Group Identifiers + +When using volumes (`-v` flags), permissions issues can arise between the host OS and the container. We avoid this issue by allowing you to specify the user `PUID` and group `PGID`. + +Ensure any volume directories on the host are owned by the same user you specify and any permissions issues will vanish like magic. + +In this instance `PUID=1000` and `PGID=1000`, to find yours use `id your_user` as below: + +```bash +id your_user +``` + +Example output: +```bash +uid=1000(your_user) gid=1000(your_user) groups=1000(your_user) +``` + ## DHCP Configurations The netboot.xyz Docker image requires the usage of a DHCP server in order to function properly. If you have an existing DHCP server, usually you will need to make some small adjustments to make your DHCP server forward requests to the netboot.xyz container. The main settings in your DHCP or router that you will typically need to set are: diff --git a/root/etc/supervisor.conf b/root/etc/supervisor.conf index 142e043..ee81096 100644 --- a/root/etc/supervisor.conf +++ b/root/etc/supervisor.conf @@ -1,33 +1,29 @@ [supervisord] nodaemon=true user=root - -[program:syslog-ng] -command=/usr/sbin/syslog-ng --foreground --no-caps -stdout_syslog=true -stdout_capture_maxbytes=1MB -priority = 1 +silent=false +logfile=/tmp/supervisord.log +pidfile=/run/supervisord.pid [program:nginx] -command = /usr/sbin/nginx -c /config/nginx/nginx.conf +command = gosu nbxyz /usr/sbin/nginx -c /config/nginx/nginx.conf startretries = 2 daemon=off priority = 2 +stdout_logfile=/dev/null +stderr_logfile=/dev/null [program:webapp] environment=NODE_ENV="production",PORT=%(ENV_WEB_APP_PORT)s -command=/usr/bin/node app.js -user=nbxyz +command=gosu nbxyz /usr/bin/node app.js directory=/app priority = 3 +stdout_logfile=/dev/null +stderr_logfile=/dev/null [program:dnsmasq] -command=/usr/sbin/dnsmasq --port=0 --keep-in-foreground --enable-tftp --user=nbxyz --tftp-secure --tftp-root=/config/menus %(ENV_TFTPD_OPTS)s -stdout_logfile=/config/tftpd.log +command=/usr/local/bin/dnsmasq-wrapper.sh %(ENV_TFTPD_OPTS)s +priority = 3 redirect_stderr=true -priority = 4 - -[program:messages-log] -command=tail -f /var/log/messages -stdout_logfile=/dev/stdout +stdout_logfile=/dev/fd/1 stdout_logfile_maxbytes=0 diff --git a/root/init.sh b/root/init.sh index 1a7ad99..6f2597a 100755 --- a/root/init.sh +++ b/root/init.sh @@ -1,5 +1,28 @@ #!/bin/bash +# Configure user and group IDs +PUID=${PUID:-1000} +PGID=${PGID:-1000} + +echo "[init] Setting up user nbxyz with PUID=${PUID} and PGID=${PGID}" + +# Create group with specified GID if it doesn't exist +if ! getent group ${PGID} > /dev/null 2>&1; then + groupadd -g ${PGID} nbxyz +else + echo "[init] Group with GID ${PGID} already exists" +fi + +# Create user with specified UID if it doesn't exist +if ! getent passwd ${PUID} > /dev/null 2>&1; then + useradd -u ${PUID} -g ${PGID} -d /config -s /bin/false nbxyz +else + echo "[init] User with UID ${PUID} already exists" +fi + +# Add to users group for compatibility +usermod -a -G users nbxyz 2>/dev/null || true + # make our folders mkdir -p \ /assets \ @@ -7,7 +30,8 @@ mkdir -p \ /config/log/nginx \ /run \ /var/lib/nginx/tmp/client_body \ - /var/tmp/nginx + /var/tmp/nginx \ + /var/log # copy config files [[ ! -f /config/nginx/nginx.conf ]] && \ @@ -15,10 +39,12 @@ mkdir -p \ [[ ! -f /config/nginx/site-confs/default ]] && \ envsubst '${NGINX_PORT}' < /defaults/default > /config/nginx/site-confs/default -# Ownership +# Set up permissions for all directories that services need to write to chown -R nbxyz:nbxyz /assets chown -R nbxyz:nbxyz /var/lib/nginx -chown -R nbxyz:nbxyz /var/log/nginx +chown -R nbxyz:nbxyz /config/log/nginx +chown -R nbxyz:nbxyz /run +chown -R nbxyz:nbxyz /var/tmp/nginx # create local logs dir mkdir -p \ diff --git a/root/start.sh b/root/start.sh index 5a1e435..8007f11 100755 --- a/root/start.sh +++ b/root/start.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Perform the initial configuration +# Perform the initial configuration as root /init.sh echo " _ _ _ " @@ -15,4 +15,7 @@ echo echo "https://opencollective.com/netbootxyz" echo "https://github.com/sponsors/netbootxyz" echo -supervisord -c /etc/supervisor.conf + +# Run supervisord as root (it will use gosu for individual programs) +echo "[start] Starting supervisord (programs will run as nbxyz)" +exec supervisord -c /etc/supervisor.conf diff --git a/root/usr/local/bin/dnsmasq-wrapper.sh b/root/usr/local/bin/dnsmasq-wrapper.sh new file mode 100644 index 0000000..c5d72d6 --- /dev/null +++ b/root/usr/local/bin/dnsmasq-wrapper.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Wrapper script for dnsmasq to ensure TFTP logs are visible in docker logs +echo "[dnsmasq] Starting TFTP server on port 69" +echo "[dnsmasq] TFTP root: /config/menus" +echo "[dnsmasq] TFTP security: enabled" +echo "[dnsmasq] Logging: enabled (dhcp and queries)" + +# Start dnsmasq via gosu with logging to stderr (which supervisord can capture) +exec gosu nbxyz /usr/sbin/dnsmasq --port=0 --keep-in-foreground --enable-tftp --user=nbxyz --tftp-secure --tftp-root=/config/menus --log-facility=- --log-dhcp --log-queries "$@" \ No newline at end of file