feat: Implement virtual hosting for multi-domain Gemini server

- Add hostname-based request routing for multiple capsules per server
- Parse virtual host configs from TOML sections ([hostname])
- Implement per-host certificate and content isolation
- Add comprehensive virtual host testing and validation
- Update docs and examples for multi-host deployments

This enables Pollux to serve multiple Gemini domains from one instance,
providing the foundation for multi-tenant Gemini hosting.
This commit is contained in:
Jeena 2026-01-22 02:38:09 +00:00
parent c193d831ed
commit 0459cb6220
22 changed files with 2296 additions and 406 deletions

80
dist/INSTALL.md vendored
View file

@ -16,20 +16,23 @@ This guide covers installing and configuring the Pollux Gemini server for produc
cargo build --release
sudo cp target/release/pollux /usr/local/bin/
# 2. Get certificates
sudo certbot certonly --standalone -d example.com
# 3. Create directories and user
# 2. Create directories and user
sudo useradd -r -s /bin/false pollux
sudo usermod -a -G ssl-cert pollux
sudo mkdir -p /etc/pollux /var/www/example.com
sudo chown -R pollux:pollux /var/www/example.com
sudo mkdir -p /etc/pollux/tls /var/gemini
sudo chown -R pollux:pollux /var/gemini
# 3. Generate certificates
sudo -u pollux openssl req -x509 -newkey rsa:4096 \
-keyout /etc/pollux/tls/key.pem \
-out /etc/pollux/tls/cert.pem \
-days 365 -nodes \
-subj "/CN=example.com"
# 4. Install config
sudo cp dist/config.toml /etc/pollux/
# 5. Add your Gemini content
sudo cp -r your-content/* /var/www/example.com/
sudo cp -r your-content/* /var/gemini/
# 6. Install and start service
sudo cp dist/pollux.service /etc/systemd/system/
@ -57,24 +60,23 @@ sudo cp target/release/pollux /usr/local/bin/
#### Certificate Setup
**For Production:** Obtain certificates from your preferred Certificate Authority and place them in `/etc/pollux/`. Ensure they are readable by the pollux user.
**For Production:** Obtain certificates from your preferred Certificate Authority and place them in `/etc/pollux/tls/`. Ensure they are readable by the pollux user.
**For Development/Testing:** Generate self-signed certificates (see Quick Start section).
**Note:** Let's Encrypt certificates can be used but their installation and permission setup is beyond the scope of this documentation.
**Note:** Let's Encrypt certificates can be used - place them under `/etc/letsencrypt/live/` and update your config accordingly.
```bash
# Generate certificates
openssl req -x509 -newkey rsa:4096 \
-keyout /etc/pollux/key.pem \
-out /etc/pollux/cert.pem \
sudo -u pollux openssl req -x509 -newkey rsa:4096 \
-keyout /etc/pollux/tls/key.pem \
-out /etc/pollux/tls/cert.pem \
-days 365 -nodes \
-subj "/CN=example.com"
# Set permissions
sudo chown pollux:pollux /etc/pollux/*.pem
sudo chmod 644 /etc/pollux/cert.pem
sudo chmod 600 /etc/pollux/key.pem
# Set permissions (already correct when run as pollux user)
sudo chmod 644 /etc/pollux/tls/cert.pem
sudo chmod 600 /etc/pollux/tls/key.pem
```
### User and Directory Setup
@ -89,8 +91,8 @@ sudo usermod -a -G ssl-cert pollux # Ubuntu/Debian
sudo usermod -a -G certbot pollux # Some systems
# Create directories
sudo mkdir -p /etc/pollux /var/www/example.com
sudo chown -R pollux:pollux /var/www/example.com
sudo mkdir -p /etc/pollux/tls /var/gemini
sudo chown -R pollux:pollux /var/gemini
```
### Configuration
@ -98,26 +100,29 @@ sudo chown -R pollux:pollux /var/www/example.com
Edit `/etc/pollux/config.toml`:
```toml
root = "/var/www/example.com"
cert = "/etc/pollux/cert.pem"
key = "/etc/pollux/key.pem"
# Global settings
bind_host = "0.0.0.0"
hostname = "example.com"
port = 1965
max_concurrent_requests = 1000
log_level = "info"
# Host configuration
["example.com"]
root = "/var/gemini"
cert = "/etc/pollux/tls/cert.pem"
key = "/etc/pollux/tls/key.pem"
```
### Content Setup
```bash
# Copy your Gemini files
sudo cp -r gemini-content/* /var/www/example.com/
sudo cp -r gemini-content/* /var/gemini/
# Set permissions
sudo chown -R pollux:pollux /var/www/example.com
sudo find /var/www/example.com -type f -exec chmod 644 {} \;
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo chown -R pollux:pollux /var/gemini
sudo find /var/gemini -type f -exec chmod 644 {} \;
sudo find /var/gemini -type d -exec chmod 755 {} \;
```
### Service Installation
@ -128,7 +133,9 @@ sudo cp dist/pollux.service /etc/systemd/system/
# If your paths differ, edit the service file
sudo editor /etc/systemd/system/pollux.service
# Update ReadOnlyPaths to match your config
# Update ReadOnlyPaths to match your config:
# - /etc/pollux for config and TLS certificates
# - /var/gemini for your content root
# Enable and start
sudo systemctl daemon-reload
@ -154,10 +161,10 @@ openssl s_client -connect example.com:1965 -servername example.com <<< "gemini:/
### Permission Issues
```bash
# Check certificate access
sudo -u pollux cat /etc/pollux/cert.pem
sudo -u pollux cat /etc/pollux/tls/cert.pem
# Check content access
sudo -u pollux ls -la /var/www/example.com/
sudo -u pollux ls -la /var/gemini/
```
### Port Issues
@ -182,13 +189,12 @@ sudo systemctl reload pollux
See `config.toml` for all available options. Key settings:
- `root`: Directory containing your .gmi files
- `cert`/`key`: TLS certificate paths
- `bind_host`: IP/interface to bind to
- `hostname`: Domain name for URI validation
- `port`: Listen port (1965 is standard)
- `max_concurrent_requests`: Connection limit
- `log_level`: Logging verbosity
- `root`: Directory containing your .gmi files (per host section)
- `cert`/`key`: TLS certificate paths (per host section)
- `bind_host`: IP/interface to bind to (global)
- `port`: Listen port (1965 is standard, per host override possible)
- `max_concurrent_requests`: Connection limit (global)
- `log_level`: Logging verbosity (global, per host override possible)
## Certificate Management

64
dist/config.toml vendored
View file

@ -5,31 +5,14 @@
#
# The Gemini protocol is specified in RFC 1436: https://tools.ietf.org/rfc/rfc1436.txt
# Directory containing your Gemini files (.gmi, .txt, images, etc.)
# The server will serve files from this directory and its subdirectories.
# Default index file is 'index.gmi' for directory requests.
#
# IMPORTANT: The server needs READ access to this directory.
# Make sure the service user (gemini) can read all files here.
root = "/var/www/example.com"
# TLS certificate and private key files
# These files are required for TLS encryption (Gemini requires TLS).
#
# For Let's Encrypt certificates (recommended for production):
# cert = "/etc/letsencrypt/live/example.com/fullchain.pem"
# key = "/etc/letsencrypt/live/example.com/privkey.pem"
#
# To obtain Let's Encrypt certs:
# sudo certbot certonly --standalone -d example.com
#
# For development/testing, generate self-signed certs:
# openssl req -x509 -newkey rsa:4096 -keyout /etc/pollux/key.pem -out /etc/pollux/cert.pem -days 365 -nodes -subj "/CN=example.com"
cert = "/etc/letsencrypt/live/example.com/fullchain.pem"
key = "/etc/letsencrypt/live/example.com/privkey.pem"
# For additional hostnames, add more sections like:
# ["blog.example.com"]
# root = "/var/gemini/blog"
# cert = "/etc/pollux/tls/blog.crt"
# key = "/etc/pollux/tls/blog.key"
# Server network configuration
#
#
# bind_host: IP address or interface to bind the server to
# - "0.0.0.0" = listen on all interfaces (default)
# - "127.0.0.1" = localhost only
@ -37,12 +20,6 @@ key = "/etc/letsencrypt/live/example.com/privkey.pem"
# - Specific IP = bind to that address only
bind_host = "0.0.0.0"
# hostname: Domain name for URI validation
# - Used to validate incoming gemini:// URIs
# - Clients must use: gemini://yourdomain.com
# - Server validates that requests match this hostname
hostname = "example.com"
# port: TCP port to listen on
# - Default Gemini port is 1965
# - Ports below 1024 require root privileges
@ -50,7 +27,7 @@ hostname = "example.com"
port = 1965
# Request limiting
#
#
# max_concurrent_requests: Maximum number of simultaneous connections
# - Prevents server overload and DoS attacks
# - Set to 0 to disable limiting (not recommended)
@ -58,11 +35,34 @@ port = 1965
max_concurrent_requests = 1000
# Logging configuration
#
#
# log_level: Controls how much information is logged
# - "error": Only errors that prevent normal operation
# - "warn": Errors plus warnings about unusual conditions
# - "info": General operational information (recommended)
# - "debug": Detailed debugging information
# - "trace": Very verbose debugging (use only for troubleshooting)
log_level = "info"
log_level = "info"
# Host configuration
# Each hostname needs its own section with root, cert, and key settings
["example.com"]
# Directory containing your Gemini files (.gmi, .txt, images, etc.)
# The server will serve files from this directory and its subdirectories.
# Default index file is 'index.gmi' for directory requests.
#
# IMPORTANT: The server needs READ access to this directory.
# Make sure the service user can read all files here.
root = "/var/gemini"
# TLS certificate and private key files
# These files are required for TLS encryption (Gemini requires TLS).
#
# For self-signed certificates (development/testing):
cert = "/etc/pollux/tls/cert.pem"
key = "/etc/pollux/tls/key.pem"
#
# Generate self-signed certs with:
# openssl req -x509 -newkey rsa:4096 -keyout /etc/pollux/tls/key.pem -out /etc/pollux/tls/cert.pem -days 365 -nodes -subj "/CN=example.com"
#
# For Let's Encrypt certificates, use paths under /etc/letsencrypt/live/

8
dist/pollux.service vendored
View file

@ -13,12 +13,10 @@ Group=pollux
NoNewPrivileges=yes
ProtectHome=yes
ProtectSystem=strict
ReadOnlyPaths=/etc/pollux /etc/letsencrypt/live/example.com /var/www/example.com
# NOTE: Adjust /etc/letsencrypt/live/example.com and /var/www/example.com to match your config
# The server needs read access to config, certificates, and content files
ReadOnlyPaths=/etc/pollux /var/gemini
# NOTE: Adjust paths to match your config:
# - /etc/letsencrypt/live/example.com for Let's Encrypt certs
# - /var/www/example.com for your content root
# - /etc/pollux for config and TLS certificates
# - /var/gemini for your content root
# The server needs read access to config, certificates, and content files
[Install]