├── .github
├── FUNDING.yml
└── workflows
│ ├── build.yml
│ └── webapp-dev.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── app.js
├── package.json
├── public
├── img
│ ├── favicon
│ │ ├── android-icon-144x144.png
│ │ ├── android-icon-192x192.png
│ │ ├── android-icon-36x36.png
│ │ ├── android-icon-48x48.png
│ │ ├── android-icon-72x72.png
│ │ ├── android-icon-96x96.png
│ │ ├── apple-icon-114x114.png
│ │ ├── apple-icon-120x120.png
│ │ ├── apple-icon-144x144.png
│ │ ├── apple-icon-152x152.png
│ │ ├── apple-icon-180x180.png
│ │ ├── apple-icon-57x57.png
│ │ ├── apple-icon-60x60.png
│ │ ├── apple-icon-72x72.png
│ │ ├── apple-icon-76x76.png
│ │ ├── apple-icon-precomposed.png
│ │ ├── apple-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon-96x96.png
│ │ ├── favicon.ico
│ │ ├── manifest.json
│ │ ├── ms-icon-144x144.png
│ │ ├── ms-icon-150x150.png
│ │ ├── ms-icon-310x310.png
│ │ └── ms-icon-70x70.png
│ └── logo.png
├── index.ejs
├── netbootxyz-web.ejs
└── vendor
│ ├── css
│ ├── bootstrap.min.css
│ ├── dataTables.bootstrap4.min.css
│ └── docs.min.css
│ └── js
│ ├── ace.js
│ ├── bootstrap.min.js
│ ├── dataTables.bootstrap4.min.js
│ ├── jquery.dataTables.min.js
│ ├── jquery.min.js
│ ├── mode-sh.js
│ ├── popper.min.js
│ └── theme-chrome.js
├── renovate.json
└── root
├── defaults
├── default
└── nginx.conf
├── donate.txt
├── etc
└── supervisor.conf
└── start.sh
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | open_collective: netbootxyz
2 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 | on:
3 | pull_request:
4 | branches:
5 | - master
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 | with:
16 | fetch-depth: '0'
17 |
18 | - name: Checkout docker-netbootxyz for container source files
19 | uses: actions/checkout@v4
20 | with:
21 | repository: netbootxyz/docker-netbootxyz
22 | path: docker-netbootxyz
23 |
24 | - name: Build the Docker image
25 | run: docker build .
26 |
--------------------------------------------------------------------------------
/.github/workflows/webapp-dev.yml:
--------------------------------------------------------------------------------
1 | name: webapp-dev
2 | on:
3 | push
4 | jobs:
5 | build:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - name: Checkout
9 | uses: actions/checkout@v4
10 |
11 | - name: Checkout docker-netbootxyz for container source files
12 | uses: actions/checkout@v4
13 | with:
14 | repository: netbootxyz/docker-netbootxyz
15 | path: docker-netbootxyz
16 |
17 | - name: Set up Docker Buildx
18 | uses: docker/setup-buildx-action@v3
19 |
20 | - name: Login to the GitHub Container Registry
21 | uses: docker/login-action@v3
22 | with:
23 | registry: ghcr.io
24 | username: ${{ secrets.GHCR_USER }}
25 | password: ${{ secrets.GHCR_TOKEN }}
26 |
27 | - name: Build and push image
28 | uses: docker/build-push-action@v6
29 | with:
30 | push: true
31 | platforms: linux/amd64,linux/arm64
32 | context: .
33 | file: ./Dockerfile
34 | tags: |
35 | ghcr.io/netbootxyz/${{ github.workflow }}:latest
36 | ghcr.io/netbootxyz/${{ github.workflow }}:${{ github.sha }}
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .c9
3 | package-lock.json
4 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.21.3
2 |
3 | # set version label
4 | ARG BUILD_DATE
5 | ARG VERSION
6 | ARG WEBAPP_VERSION
7 |
8 | LABEL build_version="netboot.xyz version: ${VERSION} Build-date: ${BUILD_DATE}"
9 | LABEL maintainer="antonym"
10 | 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."
11 |
12 | RUN \
13 | apk update && \
14 | apk upgrade && \
15 | apk add --no-cache \
16 | bash \
17 | busybox \
18 | curl \
19 | envsubst \
20 | git \
21 | jq \
22 | nghttp2-dev \
23 | nginx \
24 | nodejs \
25 | shadow \
26 | sudo \
27 | supervisor \
28 | syslog-ng \
29 | tar \
30 | dnsmasq && \
31 | apk add --no-cache --virtual=build-dependencies \
32 | npm && \
33 | groupmod -g 1000 users && \
34 | useradd -u 911 -U -d /config -s /bin/false nbxyz && \
35 | usermod -G users nbxyz && \
36 | mkdir /app \
37 | /config \
38 | /defaults
39 |
40 | COPY . /app
41 |
42 | RUN \
43 | npm install --prefix /app && \
44 | apk del --purge build-dependencies && \
45 | rm -rf /tmp/*
46 |
47 | ENV TFTPD_OPTS=''
48 | ENV NGINX_PORT='80'
49 | ENV WEB_APP_PORT='3000'
50 |
51 | EXPOSE 69/udp
52 | EXPOSE 80
53 | EXPOSE 3000
54 |
55 | COPY docker-netbootxyz/root/ /
56 |
57 | # default command
58 | CMD ["sh","/start.sh"]
59 |
60 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # netboot.xyz webapp
2 |
3 | This repo houses the netboot.xyz webapp that
4 | provides a web interface for editing iPXE files
5 | and downloading assets locally to the app.
6 |
7 | The app is versioned over time and is integrated into the docker-netbootxyz
8 | project located [here](https://github.com/netbootxyz/docker-netbootxyz).
9 |
10 | ## Building netboot.xyz webapp locally
11 |
12 | Uses the docker-netbootxyz repo for source files to avoid duplication of configs:
13 |
14 | ```bash
15 | git clone https://github.com/netbootxyz/webapp
16 | cd webapp
17 | git clone https://github.com/netbootxyz/docker-netbootxyz
18 | docker build . -t netbootxyz-webapp
19 | ```
20 |
21 | ## Running it locally
22 |
23 | ```bash
24 | docker run -d \
25 | --name=netbootxyz-webapp \
26 | -e MENU_VERSION=2.0.84 `# optional` \
27 | -p 3000:3000 `# sets webapp port` \
28 | -p 69:69/udp `# sets tftp port` \
29 | -p 8080:80 `# optional` \
30 | -v /local/path/to/config:/config `# optional` \
31 | -v /local/path/to/assets:/assets `# optional` \
32 | --restart unless-stopped \
33 | netbootxyz-webapp
34 | ```
35 |
36 | * Port 3000: Web Application
37 | * Port 8080: NGINX Webserver for local asset hosting
38 | * Port 69: TFTP server for menus/kpxe files
39 |
40 | ## Running the latest webapp-dev build
41 |
42 | To run the build that contains the latest commited changes:
43 |
44 | ```bash
45 | docker run -d \
46 | --name=netbootxyz-webapp-dev \
47 | -e MENU_VERSION=2.0.84 `# optional` \
48 | -p 3000:3000 `# sets webapp port` \
49 | -p 69:69/udp `# sets tftp port` \
50 | -p 8080:80 `# optional` \
51 | -v /local/path/to/config:/config `# optional` \
52 | -v /local/path/to/assets:/assets `# optional` \
53 | --restart unless-stopped \
54 | ghcr.io/netbootxyz/webapp-dev:latest
55 | ```
56 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | // netboot.xyz
2 | // Main Node.js app
3 |
4 | var baseurl = process.env.SUBFOLDER || '/';
5 | var app = require('express')();
6 | var { DownloaderHelper } = require('node-downloader-helper');
7 | var exec = require('child_process').exec;
8 | var express = require('express');
9 | var fs = require('fs');
10 | var http = require('http').Server(app);
11 | var io = require('socket.io')(http, {path: baseurl + 'socket.io'});
12 | var isBinaryFile = require("isbinaryfile").isBinaryFile;
13 | var path = require('path');
14 | var readdirp = require('readdirp');
15 | var fetch = require('node-fetch');
16 | var urlLib = require('url');
17 |
18 | const allowedHosts = [
19 | 's3.amazonaws.com'
20 | ];
21 | var si = require('systeminformation');
22 | const util = require('util');
23 | var { version } = require('./package.json');
24 | var yaml = require('js-yaml');
25 | var baserouter = express.Router();
26 | let ejs = require('ejs');
27 |
28 | // Disable sigs on every startup in remote boot.cfg
29 | disablesigs();
30 | function disablesigs(){
31 | var bootcfgr = '/config/menus/remote/boot.cfg';
32 | var bootcfgl = '/config/menus/local/boot.cfg';
33 | var bootcfgm = '/config/menus/boot.cfg';
34 | if (fs.existsSync(bootcfgr) && ! fs.existsSync(bootcfgl)) {
35 | var data = fs.readFileSync(bootcfgr, 'utf8');
36 | var disable = data.replace(/set sigs_enabled true/g, 'set sigs_enabled false');
37 | fs.writeFileSync(bootcfgr, disable, 'utf8');
38 | fs.writeFileSync(bootcfgm, disable, 'utf8');
39 | }
40 | }
41 |
42 | ////// PATHS //////
43 | //// Main ////
44 | baserouter.get("/", function (req, res) {
45 | res.render(__dirname + '/public/index.ejs', {baseurl: baseurl});
46 | });
47 | baserouter.get("/netbootxyz-web.js", function (req, res) {
48 | res.setHeader("Content-Type", "application/javascript");
49 | res.render(__dirname + '/public/netbootxyz-web.ejs', {baseurl: baseurl});
50 | });
51 | //// Public JS and CSS ////
52 | baserouter.use('/public', express.static(__dirname + '/public'));
53 |
54 | // Socket IO connection
55 | io.on('connection', function(socket){
56 | //// Socket Connect ////
57 | // Log Client and connection time
58 | console.log(socket.id + ' connected time=' + (new Date).getTime());
59 | socket.join(socket.id);
60 | ///////////////////////////
61 | ////// Socket events //////
62 | ///////////////////////////
63 | // When dashboard info is requested send to client
64 | socket.on('getdash', function(){
65 | var tftpcmd = '/usr/sbin/dnsmasq --version | head -n1';
66 | var nginxcmd = '/usr/sbin/nginx -v';
67 | var dashinfo = {};
68 | dashinfo['webversion'] = version;
69 | dashinfo['menuversion'] = fs.readFileSync('/config/menuversion.txt', 'utf8');
70 | fetch('https://api.github.com/repos/netbootxyz/netboot.xyz/releases/latest', {headers: {'user-agent': 'node.js'}})
71 | .then(response => {
72 | if (!response.ok) {
73 | throw new Error(`HTTP error! status: ${response.status}`);
74 | }
75 | return response.json();
76 | })
77 | .then(body => {
78 | dashinfo['remotemenuversion'] = body.tag_name;
79 | si.cpu(function(cpu) {
80 | dashinfo['cpu'] = cpu;
81 | si.mem(function(mem) {
82 | dashinfo['mem'] = mem;
83 | si.currentLoad(function(currentLoad) {
84 | dashinfo['CPUpercent'] = currentLoad.currentload_user;
85 | exec(tftpcmd, function (err, stdout) {
86 | dashinfo['tftpversion'] = stdout;
87 | exec(nginxcmd, function (err, stdout, stderr) {
88 | dashinfo['nginxversion'] = stderr;
89 | io.sockets.in(socket.id).emit('renderdash',dashinfo);
90 | });
91 | });
92 | });
93 | });
94 | });
95 | })
96 | .catch(error => {
97 | console.log('There was a problem with the fetch operation: ' + error.message);
98 | });
99 | });
100 | // When upgrade is requested run it
101 | socket.on('upgrademenus', function(version){
102 | upgrademenu(version, function(response){
103 | io.sockets.in(socket.id).emit('renderdashhook');
104 | });
105 | });
106 | socket.on('upgrademenusdev', function(version){
107 | upgrademenu(version, function(response){
108 | io.sockets.in(socket.id).emit('renderconfighook');
109 | });
110 | });
111 | // When config info is requested send file list to client
112 | socket.on('getconfig', function(){
113 | var local_files = fs.readdirSync('/config/menus/local',{withFileTypes: true}).filter(dirent => !dirent.isDirectory()).map(dirent => dirent.name);
114 | var remote_files = fs.readdirSync('/config/menus/remote',{withFileTypes: true}).filter(dirent => !dirent.isDirectory()).map(dirent => dirent.name);
115 | io.sockets.in(socket.id).emit('renderconfig',remote_files,local_files);
116 | });
117 | // When a file is requested send it's contents to the client
118 | socket.on('editgetfile', function(filename, islocal){
119 | var rootDir = '/config/menus/';
120 | var filePath = path.resolve(rootDir, filename);
121 | if (!filePath.startsWith(rootDir)) {
122 | io.sockets.in(socket.id).emit('error', 'Invalid file path');
123 | return;
124 | }
125 | var data = fs.readFileSync(filePath);
126 | var stat = fs.lstatSync(filePath);
127 | isBinaryFile(data, stat.size).then((result) => {
128 | if (result) {
129 | io.sockets.in(socket.id).emit('editrenderfile','CANNOT EDIT THIS IS A BINARY FILE',filename,'nomenu');
130 | }
131 | else {
132 | io.sockets.in(socket.id).emit('editrenderfile',data.toString("utf8"),filename,islocal);
133 | }
134 | });
135 | });
136 | // When save is requested save it sync files and return user to menu
137 | socket.on('saveconfig', function(filename, text){
138 | var rootDir = '/config/menus/local/';
139 | var filePath = path.resolve(rootDir, filename);
140 | if (!filePath.startsWith(rootDir)) {
141 | io.sockets.in(socket.id).emit('error', 'Invalid file path');
142 | return;
143 | }
144 | fs.writeFileSync(filePath, text);
145 | layermenu(function(response){
146 | var local_files = fs.readdirSync(rootDir, {withFileTypes: true}).filter(dirent => !dirent.isDirectory()).map(dirent => dirent.name);
147 | var remote_files = fs.readdirSync('/config/menus/remote', {withFileTypes: true}).filter(dirent => !dirent.isDirectory()).map(dirent => dirent.name);
148 | io.sockets.in(socket.id).emit('renderconfig', remote_files, local_files, filename, true);
149 | });
150 | });
151 | // When revert is requested delete it, sync files and return user to menu
152 | socket.on('revertconfig', function(filename){
153 | var rootDir = '/config/menus/local/';
154 | var filePath = path.resolve(rootDir, filename);
155 | if (!filePath.startsWith(rootDir)) {
156 | io.sockets.in(socket.id).emit('error', 'Invalid file path');
157 | return;
158 | }
159 | fs.unlinkSync(filePath);
160 | layermenu(function(response){
161 | var local_files = fs.readdirSync(rootDir, {withFileTypes: true}).filter(dirent => !dirent.isDirectory()).map(dirent => dirent.name);
162 | var remote_files = fs.readdirSync('/config/menus/remote', {withFileTypes: true}).filter(dirent => !dirent.isDirectory()).map(dirent => dirent.name);
163 | io.sockets.in(socket.id).emit('renderconfig', remote_files, local_files);
164 | });
165 | });
166 | // When a create file is
167 | socket.on('createipxe', function(filename){
168 | var rootDir = '/config/menus/local/';
169 | var filePath = path.resolve(rootDir, filename);
170 | if (!filePath.startsWith(rootDir)) {
171 | io.sockets.in(socket.id).emit('error', 'Invalid file path');
172 | return;
173 | }
174 | fs.writeFileSync(filePath, '#!ipxe');
175 | layermenu(function(response){
176 | var local_files = fs.readdirSync(rootDir, {withFileTypes: true}).filter(dirent => !dirent.isDirectory()).map(dirent => dirent.name);
177 | var remote_files = fs.readdirSync('/config/menus/remote', {withFileTypes: true}).filter(dirent => !dirent.isDirectory()).map(dirent => dirent.name);
178 | io.sockets.in(socket.id).emit('renderconfig', remote_files, local_files, filename, true);
179 | });
180 | });
181 | // When the endpoints content is requested send it to the client
182 | socket.on('getlocal', async function(filename){
183 | var remotemenuversion = fs.readFileSync('/config/menuversion.txt', 'utf8');
184 | var endpointsfile = fs.readFileSync('/config/endpoints.yml');
185 | var endpoints = yaml.load(endpointsfile);
186 | var localfiles = await readdirp.promise('/assets/.');
187 | var assets = [];
188 | if (localfiles.length != 0){
189 | for (var i in localfiles){
190 | assets.push('/' + localfiles[i].path);
191 | }
192 | }
193 | io.sockets.in(socket.id).emit('renderlocal',endpoints,assets,remotemenuversion);
194 | });
195 | // When remote downloads are requested make folders and download
196 | socket.on('dlremote', function(dlfiles){
197 | dlremote(dlfiles, function(response){
198 | io.sockets.in(socket.id).emit('renderlocalhook');
199 | });
200 | });
201 | // When Local deletes are requested purge items
202 | socket.on('deletelocal', function(dlfiles){
203 | for (var i in dlfiles){
204 | var file = dlfiles[i];
205 | fs.unlinkSync('/assets' + file);
206 | console.log('Deleted /assets' + file);
207 | if (fs.existsSync('/assets' + file + '.part2')) {
208 | fs.unlinkSync('/assets' + file + '.part2');
209 | console.log('Deleted /assets' + file + '.part2');
210 | }
211 | }
212 | io.sockets.in(socket.id).emit('renderlocalhook');
213 | });
214 | // When Dev Browser is requested reach out to github for versions
215 | socket.on('devgetbrowser', async function(){
216 | var api_url = 'https://api.github.com/repos/netbootxyz/netboot.xyz/';
217 | var options = {headers: {'user-agent': 'node.js'}};
218 | var releasesResponse = await fetch(api_url + 'releases', options);
219 | if (!releasesResponse.ok) {
220 | throw new Error(`HTTP error! status: ${releasesResponse.status}`);
221 | }
222 | var releases = await releasesResponse.json();
223 | var commitsResponse = await fetch(api_url + 'commits', options);
224 | if (!commitsResponse.ok) {
225 | throw new Error(`HTTP error! status: ${commitsResponse.status}`);
226 | }
227 | var commits = await commitsResponse.json()
228 | io.sockets.in(socket.id).emit('devrenderbrowser', releases, commits);
229 | });
230 | });
231 |
232 |
233 | //// Functions ////
234 |
235 | // Layer remote with local in the main tftp endpoint
236 | function layermenu(callback){
237 | var local_files = fs.readdirSync('/config/menus/local',{withFileTypes: true}).filter(dirent => !dirent.isDirectory()).map(dirent => dirent.name);
238 | var remote_files = fs.readdirSync('/config/menus/remote',{withFileTypes: true}).filter(dirent => !dirent.isDirectory()).map(dirent => dirent.name);
239 | for (var i in remote_files){
240 | var file = remote_files[i];
241 | fs.copyFileSync('/config/menus/remote/' + file, '/config/menus/' + file);
242 | }
243 | for (var i in local_files){
244 | var file = local_files[i];
245 | fs.copyFileSync('/config/menus/local/' + file, '/config/menus/' + file);
246 | }
247 | callback(null, 'done');
248 | }
249 |
250 | // Upgrade menus to specified version
251 | async function upgrademenu(version, callback){
252 | var remote_folder = '/config/menus/remote/';
253 | // Wipe current remote
254 | var remote_files = fs.readdirSync('/config/menus/remote',{withFileTypes: true}).filter(dirent => !dirent.isDirectory()).map(dirent => dirent.name);
255 | for (var i in remote_files){
256 | var file = remote_files[i];
257 | fs.unlinkSync(remote_folder + file);
258 | }
259 | // Download files
260 | var downloads = [];
261 | var rom_files = ['netboot.xyz.kpxe',
262 | 'netboot.xyz-undionly.kpxe',
263 | 'netboot.xyz.efi',
264 | 'netboot.xyz-snp.efi',
265 | 'netboot.xyz-snponly.efi',
266 | 'netboot.xyz-arm64.efi',
267 | 'netboot.xyz-arm64-snp.efi',
268 | 'netboot.xyz-arm64-snponly.efi'];
269 |
270 | // This is a commit sha
271 | if (version.length == 40){
272 | var download_endpoint = 'https://s3.amazonaws.com/dev.boot.netboot.xyz/' + version + '/ipxe/';
273 | downloads.push({'url':'https://s3.amazonaws.com/dev.boot.netboot.xyz/' + version + '/menus.tar.gz','path':remote_folder});
274 | }
275 | // This is a regular release
276 | else{
277 | var download_endpoint = 'https://github.com/netbootxyz/netboot.xyz/releases/download/' + version + '/';
278 | downloads.push({'url':download_endpoint + 'menus.tar.gz','path':remote_folder});
279 | }
280 | for (var i in rom_files){
281 | var file = rom_files[i];
282 | var url = download_endpoint + file;
283 | downloads.push({'url':url,'path':remote_folder});
284 | }
285 | // static config for endpoints
286 | downloads.push({'url':'https://raw.githubusercontent.com/netbootxyz/netboot.xyz/' + version +'/endpoints.yml','path':'/config/'});
287 | await downloader(downloads);
288 | var untarcmd = 'tar xf ' + remote_folder + 'menus.tar.gz -C ' + remote_folder;
289 | if (version.length == 40){
290 | var version = 'Development';
291 | }
292 | exec(untarcmd, function (err, stdout) {
293 | fs.unlinkSync(remote_folder + 'menus.tar.gz');
294 | fs.writeFileSync('/config/menuversion.txt', version);
295 | layermenu(function(response){
296 | disablesigs();
297 | callback(null, 'done');
298 | });
299 | });
300 | }
301 |
302 | // Grab remote files
303 | async function dlremote(dlfiles, callback){
304 | var dlarray = [];
305 | for (var i in dlfiles){
306 | var dlfile = dlfiles[i];
307 | var dlpath = '/assets' + path.dirname(dlfile);
308 | // Make destination directory
309 | fs.mkdirSync(dlpath, { recursive: true });
310 | // Construct array for use in download function
311 | var url = 'https://github.com/netbootxyz' + dlfile;
312 | dlarray.push({'url':url,'path':dlpath});
313 | }
314 | await downloader(dlarray);
315 | callback(null, 'done');
316 | }
317 |
318 | // downloader loop
319 | async function downloader(downloads){
320 | var startTime = new Date();
321 | var total = downloads.length;
322 | for (var i in downloads){
323 | var value = downloads[i];
324 | var url = value.url;
325 | var path = value.path;
326 | var dloptions = {override:true,retry:{maxRetries:2,delay:5000}};
327 | var dl = new DownloaderHelper(url, path, dloptions);
328 |
329 | dl.on('end', function(){
330 | console.log('Downloaded ' + url + ' to ' + path);
331 | });
332 |
333 | dl.on('error', function(error) {
334 | console.error('Download failed:', error);
335 | });
336 |
337 | dl.on('progress', function(stats){
338 | var currentTime = new Date();
339 | var elaspsedTime = currentTime - startTime;
340 | if (elaspsedTime > 500) {
341 | startTime = currentTime;
342 | io.emit('dldata', url, [+i + 1,total], stats);
343 | }
344 | });
345 |
346 | await dl.start().catch(error => {
347 | console.error('Download failed:', error);
348 | });
349 |
350 | const parsedUrl = urlLib.parse(url);
351 | if (!allowedHosts.includes(parsedUrl.host)){
352 | // Part 2 if exists repeat
353 | var response = await fetch(url + '.part2', {method: 'HEAD'});
354 | var urltest = response.headers.get('server');
355 | if (urltest == 'AmazonS3' || urltest == 'Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0') {
356 | var dl2 = new DownloaderHelper(url + '.part2', path, dloptions);
357 | dl2.on('end', function(){
358 | console.log('Downloaded ' + url + '.part2' + ' to ' + path);
359 | });
360 | dl2.on('progress', function(stats){
361 | var currentTime = new Date();
362 | var elaspsedTime = currentTime - startTime;
363 | if (elaspsedTime > 500) {
364 | startTime = currentTime;
365 | io.emit('dldata', url, [+i + 1,total], stats);
366 | }
367 | });
368 | await dl2.start();
369 | }
370 | }
371 | }
372 | io.emit('purgestatus');
373 | }
374 |
375 | app.use(baseurl, baserouter);
376 |
377 | // Spin up application on port 3000 or set to WEB_APP_PORT env variable
378 |
379 | const defaultPort = 3000;
380 |
381 | let port = process.env.WEB_APP_PORT;
382 |
383 | if (!Number.isInteger(Number(port)) || port < 1 || port > 65535) {
384 | console.warn(`Invalid port "${port}" in environment variable WEB_APP_PORT. Using default port ${defaultPort} instead.`);
385 | port = defaultPort;
386 | }
387 |
388 | http.listen(port, function(){
389 | console.log('listening on *:' + port);
390 | });
391 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "WebApp",
3 | "version": "0.7.5",
4 | "description": "Configuration and mirroring application for netboot.xyz stack",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/netbootxyz/webapp.git"
12 | },
13 | "author": "netboot.xyz",
14 | "license": "Apache-2.0",
15 | "homepage": "https://netboot.xyz",
16 | "dependencies": {
17 | "ejs": "3.1.10",
18 | "express": "4.21.2",
19 | "http": "0.0.0",
20 | "isbinaryfile": "5.0.4",
21 | "js-yaml": "4.1.0",
22 | "node-downloader-helper": "2.1.9",
23 | "readdirp": "3.6.0",
24 | "node-fetch": "2.7.0",
25 | "socket.io": "4.8.1",
26 | "systeminformation": "5.25.11"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/public/img/favicon/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/android-icon-144x144.png
--------------------------------------------------------------------------------
/public/img/favicon/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/android-icon-192x192.png
--------------------------------------------------------------------------------
/public/img/favicon/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/android-icon-36x36.png
--------------------------------------------------------------------------------
/public/img/favicon/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/android-icon-48x48.png
--------------------------------------------------------------------------------
/public/img/favicon/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/android-icon-72x72.png
--------------------------------------------------------------------------------
/public/img/favicon/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/android-icon-96x96.png
--------------------------------------------------------------------------------
/public/img/favicon/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/apple-icon-114x114.png
--------------------------------------------------------------------------------
/public/img/favicon/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/apple-icon-120x120.png
--------------------------------------------------------------------------------
/public/img/favicon/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/apple-icon-144x144.png
--------------------------------------------------------------------------------
/public/img/favicon/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/apple-icon-152x152.png
--------------------------------------------------------------------------------
/public/img/favicon/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/apple-icon-180x180.png
--------------------------------------------------------------------------------
/public/img/favicon/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/apple-icon-57x57.png
--------------------------------------------------------------------------------
/public/img/favicon/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/apple-icon-60x60.png
--------------------------------------------------------------------------------
/public/img/favicon/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/apple-icon-72x72.png
--------------------------------------------------------------------------------
/public/img/favicon/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/apple-icon-76x76.png
--------------------------------------------------------------------------------
/public/img/favicon/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/public/img/favicon/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/apple-icon.png
--------------------------------------------------------------------------------
/public/img/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/public/img/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/public/img/favicon/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/favicon-96x96.png
--------------------------------------------------------------------------------
/public/img/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/favicon.ico
--------------------------------------------------------------------------------
/public/img/favicon/manifest.json:
--------------------------------------------------------------------------------
1 | {"name":"App","icons":[{"src":"/android-icon-36x36.png","sizes":"36x36","type":"image/png"},{"src":"/android-icon-48x48.png","sizes":"48x48","type":"image/png"},{"src":"/android-icon-72x72.png","sizes":"48x48","type":"image/png"},{"src":"/android-icon-96x96.png","sizes":"120x120","type":"image/png"},{"src":"/android-icon-144x144.png","sizes":"144x144","type":"image/png"},{"src":"/android-icon-192x192.png","sizes":"192x192","type":"image/png"}]}
--------------------------------------------------------------------------------
/public/img/favicon/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/ms-icon-144x144.png
--------------------------------------------------------------------------------
/public/img/favicon/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/ms-icon-150x150.png
--------------------------------------------------------------------------------
/public/img/favicon/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/ms-icon-310x310.png
--------------------------------------------------------------------------------
/public/img/favicon/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/favicon/ms-icon-70x70.png
--------------------------------------------------------------------------------
/public/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netbootxyz/webapp/44c26959872ba08fe7f082f48beef17b5b3d7c7d/public/img/logo.png
--------------------------------------------------------------------------------
/public/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | netboot.xyz Configuration
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/public/netbootxyz-web.ejs:
--------------------------------------------------------------------------------
1 | // netboot.xyz
2 | // Client side javascript
3 |
4 |
5 | // Initiate a websocket connection to the server
6 | var host = window.location.hostname;
7 | var port = window.location.port;
8 | var protocol = window.location.protocol;
9 | var socket = io.connect(protocol + '//' + host + ':' + port, {path: "<%= baseurl %>socket.io"});
10 | // If the page is being loaded for the first time render in the homepage
11 | $(document).ready(function(){renderdash()})
12 |
13 |
14 | //// Dashboard Page rendering ////
15 | function renderdash(){
16 | $('#pagecontent').empty();
17 | $('#pagecontent').append('
Loading...
Getting Dashboard
');
18 | socket.emit('getdash');
19 | }
20 | socket.on('renderdash', function(response){
21 | var tftpversion = response.tftpversion;
22 | var nginxversion = response.nginxversion;
23 | var webversion = response.webversion;
24 | var menuversion = response.menuversion;
25 | var remotemenuversion = response.remotemenuversion;
26 | var cpustats = response.cpu;
27 | var cpupercent = response.CPUpercent;
28 | var memstats = response.mem;
29 | var usedmem = (memstats.active/memstats.total)*100;
30 | var totalmem = parseFloat(memstats.total/1000000000).toFixed(2);
31 | var diskbuffer = parseFloat(memstats.buffcache/1000000000).toFixed(2);
32 | if (menuversion != remotemenuversion){
33 | var upgradebutton = ''
34 | }
35 | else{
36 | var upgradebutton = ''
37 | }
38 | $('#pagecontent').empty();
39 | $('#pagecontent').append('\
40 |