diff --git a/Dockerfile b/Dockerfile index e0d89c8..d8978a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,24 @@ FROM alpine:3.22.1 # set version label -ARG BUILD_DATE -ARG VERSION -ARG WEBAPP_VERSION +ARG BUILD_DATE="10/9/2025" +ARG VERSION="0.7.4" +ARG WEBAPP_VERSION="0.7.4" LABEL build_version="netboot.xyz version: ${VERSION} Build-date: ${BUILD_DATE}" LABEL maintainer="antonym" + +LABEL org.opencontainers.image.authors="antony@mes.ser.li" +LABEL org.opencontainers.image.url="https://github.com/netbootxyz/webapp" +LABEL org.opencontainers.image.title="NetBoot.xyz WebApp" 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." +LABEL org.opencontainers.image.documentation="https://netboot.xyz/docs/docker" +LABEL org.opencontainers.image.version="${WEBAPP_VERSION}" +LABEL org.opencontainers.image.vendor="https://NetBoot.xyz" +LABEL org.opencontainers.image.licenses="Apache-2.0 license" + +RUN apk add --no-cache bash +SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"] RUN \ apk update && \ @@ -27,6 +38,7 @@ RUN \ supervisor \ syslog-ng \ tar \ + tftp-hpa \ dnsmasq && \ apk add --no-cache --virtual=build-dependencies \ npm && \ @@ -35,7 +47,7 @@ RUN \ usermod -G users nbxyz && \ mkdir /app \ /config \ - /defaults + /defaults COPY . /app @@ -48,12 +60,15 @@ ENV TFTPD_OPTS='' ENV NGINX_PORT='80' ENV WEB_APP_PORT='3000' -EXPOSE 69/udp -EXPOSE 80 -EXPOSE 3000 +EXPOSE 69/UDP 80/TCP 443/TCP +EXPOSE 3000/TCP 8080/TCP +VOLUME ["/assets", "/config"] + +COPY root/ / -COPY docker-netbootxyz/root/ / +CMD ["/start.sh"] +SHELL ["/bin/bash", "-c"] # default command -CMD ["sh","/start.sh"] +ENTRYPOINT ["/start.sh"] diff --git a/app.js b/app.js index fca6816..e8d349d 100644 --- a/app.js +++ b/app.js @@ -4,6 +4,10 @@ var baseurl = process.env.SUBFOLDER || '/'; var app = require('express')(); var { DownloaderHelper } = require('node-downloader-helper'); +const { HttpProxyAgent } = require('http-proxy-agent'); +const { HttpsProxyAgent } = require('https-proxy-agent'); +const { URL } = require('url'); +const getProxyForUrl = require('proxy-from-env').getProxyForUrl; var exec = require('child_process').exec; var express = require('express'); var fs = require('fs'); @@ -11,7 +15,7 @@ var http = require('http').Server(app); var io = require('socket.io')(http, {path: baseurl + 'socket.io'}); var isBinaryFile = require("isbinaryfile").isBinaryFile; var path = require('path'); -var readdirp = require('readdirp'); +let {readdirp} = require('readdirp'); var fetch = require('node-fetch'); var urlLib = require('url'); @@ -39,6 +43,26 @@ function disablesigs(){ } } +function getProxyAgentFromUrl(request_url, request_options=false) { + const proxy_url = getProxyForUrl(request_url); + if(!proxy_url) { + return false; + } + + const protocol = new URL(request_url).protocol; + let agent; + + if (protocol === 'https:') { + agent = new HttpsProxyAgent(proxy_url); + return request_options ? { httpsRequestOptions: { agent } } : agent; + } else if (protocol === 'http:') { + agent = new HttpProxyAgent(proxy_url); + return request_options ? { httpRequestOptions: { agent } } : agent; + } + + return false; +} + ////// PATHS ////// //// Main //// baserouter.get("/", function (req, res) { @@ -65,9 +89,11 @@ io.on('connection', function(socket){ var tftpcmd = '/usr/sbin/dnsmasq --version | head -n1'; var nginxcmd = '/usr/sbin/nginx -v'; var dashinfo = {}; + const fetch_uri = 'https://api.github.com/repos/netbootxyz/netboot.xyz/releases/latest'; + var fetch_opts = {headers:{'user-agent':'node.js'},agent:getProxyAgentFromUrl(fetch_uri,false)}; dashinfo['webversion'] = version; dashinfo['menuversion'] = fs.readFileSync('/config/menuversion.txt', 'utf8'); - fetch('https://api.github.com/repos/netbootxyz/netboot.xyz/releases/latest', {headers: {'user-agent': 'node.js'}}) + fetch(fetch_uri, fetch_opts) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); @@ -88,10 +114,10 @@ io.on('connection', function(socket){ dashinfo['nginxversion'] = stderr; io.sockets.in(socket.id).emit('renderdash',dashinfo); }); - }); + }); }); }); - }); + }); }) .catch(error => { console.log('There was a problem with the fetch operation: ' + error.message); @@ -163,7 +189,7 @@ io.on('connection', function(socket){ io.sockets.in(socket.id).emit('renderconfig', remote_files, local_files); }); }); - // When a create file is + // When a create file is socket.on('createipxe', function(filename){ var rootDir = '/config/menus/local/'; var filePath = path.resolve(rootDir, filename); @@ -183,7 +209,14 @@ io.on('connection', function(socket){ var remotemenuversion = fs.readFileSync('/config/menuversion.txt', 'utf8'); var endpointsfile = fs.readFileSync('/config/endpoints.yml'); var endpoints = yaml.load(endpointsfile); - var localfiles = await readdirp.promise('/assets/.'); + // Wrap readdirp in a promise + var localfiles = await new Promise((resolve, reject) => { + const entries = []; + readdirp('/assets/.') + .on('data', (entry) => entries.push(entry)) + .on('end', () => resolve(entries)) + .on('error', (error) => reject(error)); + }); var assets = []; if (localfiles.length != 0){ for (var i in localfiles){ @@ -213,8 +246,8 @@ io.on('connection', function(socket){ }); // When Dev Browser is requested reach out to github for versions socket.on('devgetbrowser', async function(){ - var api_url = 'https://api.github.com/repos/netbootxyz/netboot.xyz/'; - var options = {headers: {'user-agent': 'node.js'}}; + const api_url = 'https://api.github.com/repos/netbootxyz/netboot.xyz/'; + var options = {headers:{'user-agent':'node.js'},agent:getProxyAgentFromUrl(api_url,false)}; var releasesResponse = await fetch(api_url + 'releases', options); if (!releasesResponse.ok) { throw new Error(`HTTP error! status: ${releasesResponse.status}`); @@ -279,7 +312,7 @@ async function upgrademenu(version, callback){ } for (var i in rom_files){ var file = rom_files[i]; - var url = download_endpoint + file; + var url = download_endpoint + file; downloads.push({'url':url,'path':remote_folder}); } // static config for endpoints @@ -323,10 +356,11 @@ async function downloader(downloads){ var value = downloads[i]; var url = value.url; var path = value.path; - var dloptions = {override:true,retry:{maxRetries:2,delay:5000}}; + var agent = getProxyAgentFromUrl(url, true); + var dloptions = Object.assign({}, {override:true,retry:{maxRetries:2,delay:5000}}, agent); var dl = new DownloaderHelper(url, path, dloptions); - dl.on('end', function(){ + dl.on('end', function(){ console.log('Downloaded ' + url + ' to ' + path); }); @@ -350,11 +384,11 @@ async function downloader(downloads){ const parsedUrl = urlLib.parse(url); if (!allowedHosts.includes(parsedUrl.host)){ // Part 2 if exists repeat - var response = await fetch(url + '.part2', {method: 'HEAD'}); + var response = await fetch(url + '.part2',{method:'HEAD',agent:getProxyAgentFromUrl(url,false)}); var urltest = response.headers.get('server'); if (urltest == 'AmazonS3' || urltest == 'Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0') { var dl2 = new DownloaderHelper(url + '.part2', path, dloptions); - dl2.on('end', function(){ + dl2.on('end', function(){ console.log('Downloaded ' + url + '.part2' + ' to ' + path); }); dl2.on('progress', function(stats){ diff --git a/package.json b/package.json index 402fc1c..2fe2d35 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,13 @@ "ejs": "3.1.10", "express": "4.21.2", "http": "0.0.0", + "http-proxy-agent": "7.0.2", + "https-proxy-agent": "7.0.5", "isbinaryfile": "5.0.6", "js-yaml": "4.1.0", "node-downloader-helper": "2.1.9", - "readdirp": "3.6.0", + "proxy-from-env": "1.1.0", + "readdirp": "4.0.1", "node-fetch": "2.7.0", "socket.io": "4.8.1", "systeminformation": "5.27.10" diff --git a/root/defaults/default b/root/defaults/default index a5a0a08..a31c772 100644 --- a/root/defaults/default +++ b/root/defaults/default @@ -1,5 +1,5 @@ server { - listen 80; + listen ${NGINX_PORT}; location / { root /assets; autoindex on; diff --git a/root/etc/supervisor.conf b/root/etc/supervisor.conf index 986420e..1168254 100644 --- a/root/etc/supervisor.conf +++ b/root/etc/supervisor.conf @@ -15,14 +15,14 @@ daemon=off priority = 2 [program:webapp] -environment=NODE_ENV="production",PORT=3000 +environment=NODE_ENV="production",PORT="%(ENV_WEB_APP_PORT)s",HTTPS_PROXY="%(ENV_HTTPS_PROXY)s",HTTP_PROXY="%(ENV_HTTP_PROXY)s",NO_PROXY="%(ENV_NO_PROXY)s" command=/usr/bin/node app.js user=nbxyz directory=/app priority = 3 -[program:in.tftpd] -command=/usr/sbin/in.tftpd -Lvvv --user nbxyz --secure %(ENV_TFTPD_OPTS)s /config/menus +[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 redirect_stderr=true priority = 4 diff --git a/root/init.sh b/root/init.sh new file mode 100755 index 0000000..d778f6d --- /dev/null +++ b/root/init.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +set -exo pipefail +SCRIPT_DIR="$(readlink -f "$(dirname -- "${BASH_SOURCE[0]}")")" + +# Leave empty for standard flow or any other value for config recreate. +RECREATE_CONFIGURATION="${RECREATE_CONFIGURATION:-''}" + +# make config, logs, nginx etc. dirs +mkdir -p /var/lib/nginx/tmp/client_body /var/tmp/nginx \ + /config/menus/remote /config/menus/local \ + /config/nginx/site-confs /config/log/nginx /assets /run + +# Check for file exisitance, and depending on environment replace/create/nothing +[[ -n "${RECREATE_CONFIGURATION}" ]] && \ + rm -f /config/nginx/nginx.conf /config/nginx/site-confs/* + +[[ ! -f /config/nginx/nginx.conf ]] && \ + cp /defaults/nginx.conf /config/nginx/nginx.conf + +[[ ! -f /config/nginx/site-confs/default ]] && \ + envsubst < /defaults/default > /config/nginx/site-confs/default + +# Ownership +chown -R nbxyz:nbxyz /assets /var/lib/nginx /var/log/nginx + +# download menus if not found +if [[ ! -f /config/menus/remote/menu.ipxe ]]; then + echo "[netbootxyz-init] Downloading netboot.xyz at ${MENU_VERSION}" + echo "[netbootxyz-init] Downloading Menus at ${MENU_VERSION}" + curl -o /config/endpoints.yml -sL \ + "https://raw.githubusercontent.com/netbootxyz/netboot.xyz/${MENU_VERSION}/endpoints.yml" + curl -o /tmp/menus.tar.gz -sL \ + "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/menus.tar.gz" + tar xf /tmp/menus.tar.gz -C /config/menus/remote + + # boot files + echo "[netbootxyz-init] Downloading boot files at ${MENU_VERSION}" + curl -o /config/menus/remote/netboot.xyz.kpxe \ + -sL "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz.kpxe" + curl -o /config/menus/remote/netboot.xyz-undionly.kpxe \ + -sL "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz-undionly.kpxe" + curl -o /config/menus/remote/netboot.xyz.efi \ + -sL "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz.efi" + curl -o /config/menus/remote/netboot.xyz-snp.efi \ + -sL "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz-snp.efi" + curl -o /config/menus/remote/netboot.xyz-snponly.efi \ + -sL "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz-snponly.efi" + curl -o /config/menus/remote/netboot.xyz-arm64.efi \ + -sL "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz-arm64.efi" + curl -o /config/menus/remote/netboot.xyz-arm64-snp.efi \ + -sL "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz-arm64-snp.efi" + curl -o /config/menus/remote/netboot.xyz-arm64-snponly.efi \ + -sL "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz-arm64-snponly.efi" + + # layer and cleanup + echo "[netbootxyz-init] layer and cleanup " + + echo -n "${MENU_VERSION}" > /config/menuversion.txt + cp -r /config/menus/remote/* /config/menus + rm -f /tmp/menus.tar.gz +fi + +# Ownership +chown -R nbxyz:nbxyz /config diff --git a/root/start.sh b/root/start.sh index 2aab5dc..0c2d9ff 100755 --- a/root/start.sh +++ b/root/start.sh @@ -1,79 +1,7 @@ #!/bin/bash -# make our folders -mkdir -p \ - /assets \ - /config/nginx/site-confs \ - /config/log/nginx \ - /run \ - /var/lib/nginx/tmp/client_body \ - /var/tmp/nginx - -# copy config files -[[ ! -f /config/nginx/nginx.conf ]] && \ - cp /defaults/nginx.conf /config/nginx/nginx.conf -[[ ! -f /config/nginx/site-confs/default ]] && \ - cp /defaults/default /config/nginx/site-confs/default - -# Ownership -chown -R nbxyz:nbxyz /assets -chown -R nbxyz:nbxyz /var/lib/nginx -chown -R nbxyz:nbxyz /var/log/nginx - -# create local logs dir -mkdir -p \ - /config/menus/remote \ - /config/menus/local - -# download menus if not found -if [[ ! -f /config/menus/remote/menu.ipxe ]]; then - if [[ -z ${MENU_VERSION+x} ]]; then \ - MENU_VERSION=$(curl -sL "https://api.github.com/repos/netbootxyz/netboot.xyz/releases/latest" | jq -r '.tag_name') - fi - echo "[netbootxyz-init] Downloading netboot.xyz at ${MENU_VERSION}" - # menu files - curl -o \ - /config/endpoints.yml -sL \ - "https://raw.githubusercontent.com/netbootxyz/netboot.xyz/${MENU_VERSION}/endpoints.yml" - curl -o \ - /tmp/menus.tar.gz -sL \ - "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/menus.tar.gz" - tar xf \ - /tmp/menus.tar.gz -C \ - /config/menus/remote - # boot files - curl -o \ - /config/menus/remote/netboot.xyz.kpxe -sL \ - "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz.kpxe" - curl -o \ - /config/menus/remote/netboot.xyz-undionly.kpxe -sL \ - "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz-undionly.kpxe" - curl -o \ - /config/menus/remote/netboot.xyz.efi -sL \ - "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz.efi" - curl -o \ - /config/menus/remote/netboot.xyz-snp.efi -sL \ - "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz-snp.efi" - curl -o \ - /config/menus/remote/netboot.xyz-snponly.efi -sL \ - "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz-snponly.efi" - curl -o \ - /config/menus/remote/netboot.xyz-arm64.efi -sL \ - "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz-arm64.efi" - curl -o \ - /config/menus/remote/netboot.xyz-arm64-snp.efi -sL \ - "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz-arm64-snp.efi" - curl -o \ - /config/menus/remote/netboot.xyz-arm64-snponly.efi -sL \ - "https://github.com/netbootxyz/netboot.xyz/releases/download/${MENU_VERSION}/netboot.xyz-arm64-snponly.efi" - # layer and cleanup - echo -n ${MENU_VERSION} > /config/menuversion.txt - cp -r /config/menus/remote/* /config/menus - rm -f /tmp/menus.tar.gz -fi - -# Ownership -chown -R nbxyz:nbxyz /config +# Perform the initial configuration +/init.sh echo " _ _ _ " echo " _ __ ___| |_| |__ ___ ___ | |_ __ ___ _ ____ "