Node.js

From Gentoo Wiki
(Redirected from Node)
Jump to:navigation Jump to:search
This article is a stub. Please help out by expanding it - how to get started.

Node.js is a cross platform, open source, JavaScript server environment.

Installation

USE flags

USE flags for net-libs/nodejs A JavaScript runtime built on Chrome's V8 JavaScript engine

+icu Enable ICU (Internationalization Components for Unicode) support, using dev-libs/icu
+npm Enable NPM package manager
+snapshot Enable snapshot creation for faster startup
+ssl Add support for SSL/TLS connections (Secure Socket Layer / Transport Layer Security)
+system-icu Use system dev-libs/icu instead of the bundled version
+system-ssl Use system OpenSSL instead of the bundled one
corepack Enable the experimental corepack package management tool
debug Enable extra debug codepaths, like asserts and extra output. If you want to get meaningful backtraces see https://wiki.gentoo.org/wiki/Project:Quality_Assurance/Backtraces
doc Add extra documentation (API, Javadoc, etc). It is recommended to enable per package instead of globally
inspector Enable V8 inspector
lto Enable Link-Time Optimization (LTO) to optimize the build
pax-kernel Enable building under a PaX enabled kernel
systemtap Enable SystemTap/DTrace tracing
test Enable dependencies and/or preparations necessary to run tests (usually controlled by FEATURES=test but can be toggled independently)

npm

Node.js has a USE flag to include npm, the Node.js package manager. npm is necessary to install a Node.js application's dependencies, which are defined in a file named package.json. The USE can be disabled if npm is not necessary locally, or prefer to only install an alternative, for example, sys-apps/yarn.

Don't let anything except Portage install stuff to /usr - for safe coliving for Portage:

FILE ~/.config/bash/bashrc
export NPM_CONFIG_PREFIX=$HOME/.local/
export PATH="/home/$USER/go/bin:/home/$USER/.local/bin:$NPM_CONFIG_PREFIX/bin:$PATH"
# We have this against messing with Portage files.
# Bonus: now you can `npm install -g` without root.
# According to
# https://wiki.g.o/wiki/Node.js#npm
# https://stackoverflow.com/a/63026107/1879101
# https://www.reddit.com/r/Gentoo/comments/ydzkml/nodejs_is_it_ok_to_install_global_packages/

See also https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally#manually-change-npms-default-directory

Standalone Node.js server

Warning
Node.js and Express.js suggest to run Node.js behind a reverse proxy to mitigate DoS attacks and improve performance. [1][2][3] This section goes against these recommendations and even includes an example of using Node.js itself as a reverse proxy. Not following these recommendations is allowed if the server will not be public. With caution, the methods provided here can be used for small homelab projects. It is also important to understand that reverse proxies are not a panacea. They may contain vulnerabilities that could lead to the execution of arbitrary code (e.g. bug #CVE-2021-23017) or root privilege escalation (e.g. bug #CVE-2016-1247). Not using reverse proxies limits the attack vector to Node.js only.

Node.js can be run as a standalone HTTP server. It does not require root privileges and can be accessed from the Internet, for example on port 3000.

To launch the official example, the hostname (a variable in the example) must be set to a public IPv6 or IPv4 address (localhost will not work). The modified example can be executed from the user space as following:

user $node modified-downloaded-example.js

The only problem is the inability to connect to well-known ports (e.g. 80) from unprivileged user space. But this problem can be solved with port redirection.

SELinux policy

Warning
The policy provided in this section is not official. Review the policy before using it.

As of December 2, 2024, the Node.js package does not come with a SELinux policy, so creating a custom policy is required. The following custom policy assumes that Node.js will be executed from unprivileged user space. The policy was tested with Nodejs v. 22.7.0 on the default/linux/arm64/23.0/musl/hardened/selinux profile.

FILE nodejs.te
# License: 0BSD

policy_module(nodejs, 1.0)

gen_require(`
  attribute file_type, non_security_file_type, non_auth_file_type;
  role user_r;
  type user_t;
  type node_t;
  type cert_t;
  type sshd_t;
  type user_devpts_t;
  type ntop_port_t;
  type unreserved_port_t;
')

##
# Type declarations.
#
type nodejs_t;
type nodejs_exec_t;
type nodejs_www_t;
domain_type(nodejs_t)
domain_entry_file(nodejs_t, nodejs_exec_t)
typeattribute nodejs_www_t file_type, non_security_file_type, non_auth_file_type;

##
# Domain transition (user_t -> nodejs_t).
#
domtrans_pattern(user_t, nodejs_exec_t, nodejs_t)
role user_r types nodejs_t;
allow user_t nodejs_exec_t:file mmap_exec_file_perms;

##
# Static files.
#
allow nodejs_t nodejs_www_t:file read_file_perms;
allow nodejs_t nodejs_www_t:dir search_dir_perms;

##
# Node.js requirements.
#
allow nodejs_t self:process { execmem getcap signal };
allow nodejs_t self:fifo_file rw_file_perms;
allow nodejs_t self:tcp_socket server_stream_socket_perms;
allow nodejs_t node_t:tcp_socket node_bind;
allow nodejs_t ntop_port_t:tcp_socket name_bind;
# OpenSSL
allow nodejs_t cert_t:dir search;
allow nodejs_t cert_t:file read_file_perms;
# PTY
allow nodejs_t sshd_t:fd use;
allow nodejs_t user_devpts_t:chr_file rw_inherited_file_perms;

##
# Optional
#
allow nodejs_t unreserved_port_t:tcp_socket name_bind;
Important
Delete the following lines if there are no plans to use Node.js on ports other than 3000:

type unreserved_port_t; and

allow nodejs_t unreserved_port_t:tcp_socket name_bind;
FILE nodejs.fc
/usr/bin/node -- gen_context(system_u:object_r:nodejs_exec_t)

Policy installation

To compile and install the policy module, run the commands:

root #make -f /usr/share/selinux/strict/include/Makefile nodejs.pp
root #semodule --install nodejs.pp

Policy usage

The policy requires that all files that should be accessible to Node.js be labeled as nodejs_www_t.

For example, if the project is stored in the /opt/website directory, the following command can be used:

root #chcon --recursive --type nodejs_www_t /opt/website

Policy removal

To remove the policy, run the command:

root #semodule --remove nodejs

Port redirection

This section describes a way to redirect ports using the legacy iptables approach or the modern nftables approach. Choose one.

iptables

The redirection requires the following option to be enabled in the kernel:

KERNEL Enable redirections
[*] Networking support  --->
  --- Networking support
    Networking options  --->
      [*] Network packet filtering framework (Netfilter)  --->
        --- Network packet filtering framework (Netfilter)
          Core Netfilter Configuration  --->
            [*] REDIRECT target support
Important
The examples below assume that the server is running on a public IPv6 address. For an IPv4 address use iptables instead of ip6tables, the syntax is the same. IPv4 and IPv6 have separate NAT tables.

Assuming the Node.js server is running on port 3000, run the following command to redirect port 80 to 3000:

root #ip6tables --table nat --append PREROUTING --protocol tcp --dport 80 --jump REDIRECT --to-port 3000

The server should be immediately accessible via port 80.

Note
The created rule will disappear after a reboot.

To see the modified NAT table, run the command:

root #ip6tables --table nat --list
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
REDIRECT   tcp  --  anywhere             anywhere             tcp dpt:http redir ports 3000

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination

To remove the added rule (to change the port or because of a mistake), run the command:

Important
Make sure the rule is the first (1) in the PREROUTING chain, otherwise specify the correct number.
root #ip6tables --table nat --delete PREROUTING 1

nftables

The redirection requires the following options to be enabled in the kernel:

KERNEL Enable redirections
[*] Networking support  --->
  --- Networking support
    Networking options  --->
      [*] Network packet filtering framework (Netfilter)  --->
        --- Network packet filtering framework (Netfilter)
          Core Netfilter Configuration  --->
            [*] Netfilter nf_tables support
            [*]   Netfilter nf_tables redirect support
            [*]   Netfilter nf_tables nat module
KERNEL IPv6
[*] Networking support  --->
  --- Networking support
    Networking options  --->
      [*] Network packet filtering framework (Netfilter)  --->
        --- Network packet filtering framework (Netfilter)
          IPv6: Netfilter Configuration  --->
            [*] IPv6 nf_tables support
KERNEL IPv4
[*] Networking support  --->
  --- Networking support
    Networking options  --->
      [*] Network packet filtering framework (Netfilter)  --->
        --- Network packet filtering framework (Netfilter)
          IP: Netfilter Configuration  --->
            [*] IPv4 nf_tables support
Important
The examples below assume that the server is running on a public IPv6 address. For an IPv4 address use inet or ip instead of ip6

Create the NAT table and chain:

root #nft add table ip6 nat
root #nft add chain ip6 nat prerouting '{ type nat hook prerouting priority 0; }'

Redirect port 80 to port 3000:

root #nft add rule ip6 nat prerouting tcp dport 80 counter redirect to 3000

The server should be immediately accessible via port 80.

Note
The created rule will disappear after a reboot.

To see the prerouting chain, run the command:

root #nft --handle list chain ip6 nat prerouting
table ip6 nat {
	chain prerouting { # handle 1
		type nat hook prerouting priority filter; policy accept;
		tcp dport 80 counter packets 0 bytes 0 redirect to :3000 # handle 2
	}
}

To remove the added rule (to change the port or because of a mistake), run the command:

Important
Make sure the rule is matched as # handle 2 in the above output, otherwise specify the correct number.
root #nft delete rule ip6 nat prerouting handle 2

HTTPS

This section relies on Express.js because it provides a simple way to host static files that appear dynamically. All paths match the acme-tiny configuration guide, but there are no strict requirements, the files can be anywhere.

Certificate issuance (Let's Encrypt)

First, it is necessary to create and run a server script that will host the Let's Encrypt token for the HTTP-01 challenge:

FILE http-server.js
const express = require('express');

const PORT = 3000;
const ACME_CHALLENGE_PATH = '/var/www/localhost/acme-challenge';
const app = express();

app.use('/.well-known/acme-challenge', express.static(ACME_CHALLENGE_PATH));

app.listen(PORT);

Then redirect port 80 to port 3000 as described above. Install acme-tiny as described here and issue the certificate as described here.

Certificate usage

Once the certificate has been issued, the server script needs to be replaced with this one:

FILE https-server.js
const fs = require('node:fs');
const https = require('node:https');
const express = require('express');

const PORT = 3000;
const CERTIFICATE_PATH = '/var/lib/letsencrypt/chained.pem';
const PRIVATE_KEY_PATH = '/var/lib/letsencrypt/domain.key';
const app = express();

const options = {
  cert: fs.readFileSync(CERTIFICATE_PATH),
  key: fs.readFileSync(PRIVATE_KEY_PATH)
};

https.createServer(options, app).listen(PORT);

Redirect port 443 to 3000 as described above. The connection should now be encrypted. The above script doesn't actually require Express.js anymore, but it's left as an example, an example of pure Node.js can be found here. The ACME challenge is not required either, even for renewals.

Node.js as a reverse proxy for Forgejo\Gitea (or anything else)

The simplest way to set up a reverse proxy is to use Express.js with express-http-proxy.

The following example shows a way to redirect all requests coming to http://<DOMAIN>/projects to Forgejo (or Gitea). The example assumes that port 80 is redirected to port 3000 as described above.

The minimal Forgejo configuration:

FILE <Forgejo\Gitea root directory>/custom/conf/app.ini
[server]
ROOT_URL = http://<DOMAIN GOES HERE>/projects/
HTTP_PORT = 3001
Note
Replace http:// with https:// if HTTPS is used.

The minimal HTTP server:

FILE server.js
const express = require('express');
const proxy = require('express-http-proxy');

const PORT = 3000;
const app = express();

app.use('/projects', proxy('localhost:3001'));

app.listen(PORT);

To use HTTPS, just inject the following lines in the script provided here:

CODE
const proxy = require('express-http-proxy');
app.use('/projects', proxy('localhost:3001'));

Web application daemons with nginx and monit

This section will walk through installing Node.js behind nginx and using Monit to keep Node instances alive. Since Node.js is a single-process application, the goal is to launch multiple instances of the application and load balance using nginx.

Packages

Use app-admin/monit for spawning Node.js servers.

root #emerge --ask monit nginx nodejs

Configure Monit

FILE /etc/monit.d/my-appAuto restart NodeJS App
check process mysql with pidfile /var/run/my-app/mysqld.pid
    start program = "/bin/bash -c 'rc-service mysql start'"
    stop program  = "/bin/bash -c 'rc-service mysql stop'"

Configure Nginx

FILE /etc/nginx/nginx.confNginx Config
http {
    upstream myapp1 {
        least_conn;
        server srv1.example.com;
        server srv2.example.com;
        server srv3.example.com;
    }
 
    server {
        listen 80;

        location / {
            proxy_pass http://myapp1;
        }
    }
}

Web application with openrc runscript

FILE /etc/init.d/nodejs-serverSample init.d file for a Node.js daemon
#!/sbin/openrc-run
 
user="nobody"
group="nobody"
command="/usr/bin/node"
directory="/opt/${RC_SVCNAME}"
command_args="httpd.js"
command_user="${user}:${group}"
command_background="yes"
pidfile="/run/${RC_SVCNAME}.pid"
output_log="/var/log/${RC_SVCNAME}.log"
error_log="${output_log}"
 
depend() {
	use net
}

References