Skip to content

itsbjoern/roost

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Roost

Release CI

A local HTTPS reverse proxy that manages certificate authorities, signed domain certificates, and hosts file entries. Use real HTTPS for local development without manual cert setup.

Install

Install via npm (recommended, requires Node.js 18+):

npm install -g @itsbjoern/roost
roost init

This installs the roost CLI globally and downloads a prebuilt binary for your platform from the GitHub Releases of this repository.

Download a release binary:

  1. Go to Releases and download the archive for your platform (Linux, macOS, or Windows).
  2. Extract the roost binary to a directory in your PATH.
  3. Run roost init to complete setup.

Build from source (requires Rust):

git clone https://github.com/itsbjoern/roost.git && cd roost
cargo build --release
./target/release/roost init

Quick start

roost init                    # One-time setup (creates CA, installs to trust store)
roost domain add example.local    # Add a domain (creates cert, updates /etc/hosts)
roost serve config add example.local 5001
roost serve                   # Start the proxy

Visit https://example.local — it proxies to http://localhost:5001. Port 80 redirects to HTTPS.

Without root/sudo? Use a non-privileged port:

roost serve config ports add 8443
roost serve
# Visit https://example.local:8443

Use certs directly in a local server (no proxy)

You can add a domain and use its certs in your own dev server—no proxy needed. Roost creates the cert, updates hosts, and you get the paths:

roost domain add example.local
# Get paths for your server config:
roost domain path cert example.local   # → ~/.roost/certs/example.local.pem
roost domain path key example.local    # → ~/.roost/certs/example.local-key.pem

# Create the domain if it doesn't exist:
roost domain path cert example.local --generate

Point your local server at those paths. Using the JS API (recommended)—get the literal cert and key contents in one call:

// vite.config.ts
import { defineConfig } from 'vite';
import { getDomainCerts } from '@itsbjoern/roost';

export default defineConfig(async () => {
  const { cert, key } = await getDomainCerts('example.local', { generate: true });
  return {
    server: {
      https: { cert, key },
      allowedHosts: ["example.local"],
      // Force Vite to use HTTPS with HTTP/1.1 (not HTTP/2) so WebSocket upgrade works for HMR.
      // When proxy is set, Vite uses https.createServer; otherwise it uses http2.createSecureServer
      // which does not emit "upgrade", so wss:// connections fail.
      proxy: {},
      hmr: {
        host: "example.local",
        protocol: "wss",
      },
  };
});

Or with Node's https.createServer:

const https = require('https');
const { getDomainCerts } = require('@itsbjoern/roost');

(async () => {
  const { cert, key } = await getDomainCerts('example.local', { generate: true });
  const server = https.createServer(
    { cert, key },
    (req, res) => { /* ... */ }
  );
  server.listen(5173);
})();

Your dev server then serves HTTPS directly at https://example.local:5173 using Roost's trusted certs. See JavaScript API for all helpers.

What it does

  • CA management: Create and install a root CA into your system trust store so browsers accept local certs
  • Domain management: Add domains (e.g. api.example.local); Roost creates certs, updates /etc/hosts, and handles renewal
  • Reverse proxy: Terminates TLS and forwards https://api.example.local to http://localhost:5001; explicit ports in the URL (e.g. https://api.example.local:5173) forward to that backend port
  • Daemon mode: Run the proxy in the background; reload config without restarting

Port configuration

Ports are configured in .roostrc and default to 80 and 443:

  • Port 80 (when 443 is configured): Plain HTTP redirect to HTTPS
  • Port 443 and others: TLS proxy to your backends
roost serve config ports add 5173     # Add Vite, etc.
roost serve config ports list        # Show configured ports

When you use an explicit port in the URL (e.g. https://example.local:5173), the proxy forwards directly to that backend port.

Permissions

Action Required
CA install / uninstall Admin (macOS: osascript; Linux: sudo)
Hosts file edit Admin (same escalation)
Port 80 / 443 Root or CAP_NET_BIND_SERVICE
Port 8443+ None

Commands

Command Description
roost init One-time setup
roost doctor Check configuration health (CA, hosts, certs, trust store)
roost ca list List CAs (shows installed status)
roost ca create <name> Create a new CA
roost ca install [name] Install CA into system trust store
roost ca uninstall [name] Remove CA from trust store
roost domain add <domain> Add domain, create cert, update hosts. Use --exact for no wildcard; --allow to bypass TLD allowlist
roost domain list List registered domains
roost domain path cert <domain>, key <domain> Print path to cert or key file. Use --generate to create the domain if it doesn't exist
roost serve Start proxy (foreground)
roost serve config add <domain> <port> Map domain to port. Use --global to write to user config instead of project
roost serve config remove <domain> Remove mapping. Use --global for user config
roost serve config list List mappings (shows project or global source per mapping)
roost serve config ports add/remove/set Manage listen ports. Use --global for user config
roost serve daemon start Run proxy in background

Run roost --help or roost <cmd> --help for full usage.

JavaScript API

When you install @itsbjoern/roost as a dependency, you can call Roost from Node instead:

const { runRoost, getDomainPath, getDomainCertPath, getDomainKeyPath, getDomainPaths, getDomainCerts } = require('@itsbjoern/roost');
// or: import { getDomainCerts, getDomainPaths } from '@itsbjoern/roost';
Function Description
runRoost(args, options?) Run the roost binary with the given args. Returns Promise<{ stdout, stderr }>.
getDomainCerts(domain, options?) Build-tool friendly. Resolve both cert and key contents (PEM strings). Returns Promise<{ cert: string, key: string }>. Pass directly to HTTPS config—no file reads. Use generate: true to create the domain if missing.
getDomainPaths(domain, options?) Resolve both cert and key paths (filesystem paths). Returns Promise<{ cert: string, key: string }>.
getDomainPath('cert' | 'key', domain, options?) Resolve the path to a domain's cert or key file.
getDomainCertPath(domain, options?) Resolve the path to a domain's certificate file.
getDomainKeyPath(domain, options?) Resolve the path to a domain's private key file.

Path options (all optional):

  • generate: If true, runs roost domain add when the domain doesn't exist, so you get valid paths without a separate setup step.
  • exact: When using generate, create a cert for the exact domain only (no wildcard).
  • allow: When using generate, allow any TLD (bypass the allowlist).

Global vs project config

Domain→port mappings and listen ports live in .roostrc in one of two places:

  • Project (default): .roostrc in the current working directory. Best for per-project config you’ll commit to the repo. Use when adding mappings from within a project.
  • Global: ~/.roost/.roostrc (or %APPDATA%\roost\.roostrc on Windows). User-wide config that applies in any directory. Use for personal defaults or domains you use across many projects.

When you run roost serve, both configs are merged. For mappings, project overrides global when the same domain exists in both. For ports, the sets are combined. Use roost serve config list to see which file each mapping comes from. Add --global to config add, config remove, and ports add/remove/set to target the global file instead of the project.

Features

  • TLD allowlist: Only .test, .local, .dev, etc. by default; use --allow to override
  • Wildcard certs: domain add foo.local covers foo.local and *.foo.local; use --exact to disable
  • Config merge: Project and global .roostrc merge when you serve; see Global vs project config
  • Daemon: roost serve daemon start|stop|status|reload; add/remove mappings triggers reload when running
  • Auto renewal: Certs expiring within 30 days are regenerated automatically

Configuration

Data directory (~/.roost or %APPDATA%\roost on Windows):

~/.roost/
  config.toml    # domain -> CA mapping
  ca/            # CAs (ca.pem, ca-key.pem per CA)
  certs/         # Domain certs (domain.pem, domain-key.pem)
  daemon.json    # Daemon state when running

.roostrc (project or global) defines domain→port mappings and listen ports. Project: <cwd>/.roostrc. Global: ~/.roost/.roostrc.

[serve]
[[serve.mappings]]
domain = "api.example.local"
port = 5001

ports = [80, 443]   # optional; defaults to [80, 443]

Environment variables:

Variable Purpose
ROOST_HOME Override data directory
ROOST_SKIP_TRUST_INSTALL Skip trust store install in roost init (CI/testing)
ROOST_HOSTS_FILE Override hosts file path (testing)

Releasing

./scripts/release.sh patch    # 0.0.X – bug fixes
./scripts/release.sh minor    # 0.X.0 – new features
./scripts/release.sh major    # X.0.0 – breaking changes

The script bumps the version, commits, tags, and optionally pushes. Add -y to push without prompting. Pushing a tag triggers the release workflow to build binaries for Linux, macOS, and Windows.

About

Local HTTPS reverse proxy for development. Manages CAs, signs domain certs, updates /etc/hosts—use real HTTPS on localhost without manual cert setup.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors