npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@union.io/union

v1.0.0

Published

a node web server made for multi-domain hosting

Downloads

4

Readme

union

union is a nodejs web server created to serve multiple domains from a single host machine. it was originally developed to replace simple nginx/apache setups with express. some important features are:

  • written in node, so it can be installed & run using npm
  • can be called with no configuration in a traditional /var/www/vhosts-style directory, and will serve each domain it finds there
  • uses express as its web server, so optional per-domain configurations can be written just like normal express apps
  • uses pm2 to manage a daemon, so the server will restart if it crashes, and can run forever by default
  • serves https with minimal configuration
  • can serve single domains too
  • includes instructions for auto-starting the server when the machine boots, opening port :80/:443, and more

union combines the ease & lightweight config of a modern express server with the sturdiness and standard multi-domain capabilities of a traditional httpd server. you might even call it a union of the two approaches.

examples

let's say you have a traditional /var/www/vhosts directory, and it looks like this:

├── alpha.com
│   └── html
│       └── index.html
├── bravo.com
│   └── index.html
└── charlie.com
    └── index.html

run the following:

  1. npm init
  2. npm install @union.io/union
  3. npx union

running these three commands in your /var/www/vhosts directory is all you need to do. union will detect the three directories that look like domain names, and serve them as three separate domains with their own document roots by default. it will also handle the case where an html/ directory is present and serve that instead (like in the alpha.com example above).

let's say you have a structure that looks like this:

├── alpha.com
│   ├── app
│   │   ├── index.html
│   │   └── compiled-index.js
│   └── src
│       └── App.tsx
├── bravo.com
│   ├── index.html
│   └── private
│       └── secret.key
└── charlie.com
    └── index.html

in this example you might want to serve alpha.com from its ./app directory, as a single page app. you might also want to NOT serve bravo.com/private. and you might want to make charlie.com password-protected. this is how you'd do that:

 ├── alpha.com
+│   ├── .union.config.js
 │   ├── app
 │   │   ├── index.html
 │   │   └── compiled-index.js
 │   └── src
 │       └── App.tsx
 ├── bravo.com
 │   ├── index.html
 │   └── private
+│       ├── .do-not-serve
 │       └── secret.key
 └── charlie.com
+│       ├── .union-password
         └── index.html
  1. add a .union.config.js file in the root of the domain you want to use a special config for. union will automatically find that file and use that to serve the domain name it's in. for a single-page app, here's an example of the config file you could use (which just creates an express server):
// include express
const express = require('express');

// then configure an express app like usual
const path = require('path');
const app = express();

// Serve static files from the app directory
app.use(express.static(path.join(__dirname, 'app')));

// standard "catchall" handler for single-page apps
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'app', 'index.html'));
});

// export your express server. union will know what to do with it.
module.exports = app;

or, here's an example of how to blanket-enable CORS:

// include express
const express = require('express');
const cors = require('cors');

// then configure an express app like usual
const app = express();

// allow cors
app.use('*', cors({ origin: true }));

// export your express server. union will know what to do with it.
module.exports = app;
  1. putting a blank .do-not-serve file in a directory will keep union from serving that entire directory
$ touch bravo.com/private/.do-not-serve
  1. putting a plaintext file named .union-password in a directory will make that directory password-protected. it's kind of a shortcut for traditional .htaccess/.htpasswd files in other httpd servers. a .union-password file looks like this:
someuser:somepassword
otheruser:otherpassword

union can serve https traffic when certiticates are provided in a standard way. first, make a sibling directory to your domains folders and call it certificates:

 ├── alpha.com
 │   └── html
 │       └── index.html
 ├── bravo.com
 │   └── index.html
 ├── charlie.com
 │  └── index.html
+└── certificates

then, create symlinks inside certificates. each one should be named ${domain name} and should point to where the ssl certificates for that domain live on your server. for example, if you use letsencrypt, you might need to run this to create the symlink:

$ cd certificates
$ ln -s /etc/letsencrypt/live/alpha.com alpha.com

(the target of the symlink should be a directory containing the certificate files fullchain.pem and privkey.pem.)

 ├── alpha.com
 │   └── html
 │       └── index.html
 ├── bravo.com
 │   └── index.html
 ├── charlie.com
 │  └── index.html
 └── certificates
+    └── alpha.com -> /etc/letsencrypt/live/alpha.com

note that permissions will be tricky for most setups, if you aren't doing all of this as root. (everyone says never to use root for anything but my personal opinion is that root is fine for lots of cases where a host is only handling standard server operations like this.) for more info see permissions.


cli

npx union

starts or restarts the server. do this at startup, or after you add or remove a domain. optionally you can include a port number like this: npx union 8080 to run the HTTP server on a custom port. (it defaults to :80.)

npx union status

gives the status of the server.

npx union stop

stops the server.

npx union lint

loads all of your custom configs and confirms they are valid express apps. gives you verbose errors if not, including potential permissions problems.

npx union help

shows the help screen.


permissions

there are a few places where running a traditional web server requires elevated permissions. here are a few considerations you should be aware of:

echo 'net.ipv4.ip_unprivileged_port_start=0' | sudo tee /etc/sysctl.d/50-unprivileged-ports.conf && sudo sysctl --system

note: this allows any user on your system to open processes on these lower numbered ports. although, in my opinion, if you have compromised users on your system you've got bigger problems than whether they run something on port 80 or 3000.

  1. create the service file

create a new systemd service file for your server:

sudo vim /etc/systemd/system/union.service

here's an example of that file:

[Unit]
Description=union
After=network-online.target multi-user.target

[Service]
Type=forking
User=<your user>
Group=<your user's group>
WorkingDirectory=<path to your domains directory; e.g. /var/www/vhosts>
ExecStart=/usr/bin/npx union
Restart=on-failure

[Install]
WantedBy=multi-user.target
  1. enable and start union after creating the service file, enable it to start at boot and then start the service immediately with the following commands:
sudo systemctl enable union
sudo systemctl start union

one way around this would be to adjust permissions on the directories that contain the certificate files, giving some group read-access, and adding your user to that group. here's how to do that on a typical linux distribution with a typical letsencrypt/certbot setup.

oh, please note: adjusting permissions like this can potentially expose your SSL certificates to other users who have access to your system and can add themselves to your group. but again, if your host has been compromised and malicious users already have access to adding themselves to arbitrary groups, you have much bigger problems, imo.

  1. add your non-privileged user to a specific group (e.g., www-data), which will be granted access to the certificates
sudo usermod -a -G www-data <your user>
  1. change the group ownership of the /etc/letsencrypt/archive/ and /etc/letsencrypt/live/ directories to www-data (or your chosen group)
sudo chown -R root:www-data /etc/letsencrypt/archive/
sudo chown -R root:www-data /etc/letsencrypt/live/
  1. set the permissions on the two main letsencrypt directories so that group members can read the files within them
sudo chmod 750 /etc/letsencrypt/{live,archive}