| .dockerignore | ||
| .env.example | ||
| app.py | ||
| docker-compose.yml | ||
| Dockerfile | ||
| nginx.conf | ||
| README.md | ||
| requirements.txt | ||
| supervisord.conf | ||
Stone Soup Cluster: Web Node
The Stone Soup Cluster is a decentralized web hosting and data storage cluster operated by Secret Server Club. Web Nodes are redundant webservers scattered around Los Angeles (and hopefully, eventually, the world) which share a virtual private network created using Headscale. Web nodes serve websites through a VPS gateway on Digital Ocean (named Dusty) who routes traffic to the internet.
Dusty also host a simple site creator and deployment tool which can clone websites from a Git repo and add them to the registry of sites served on the network, automatically cloning the code to every webserver in the network.
This project is in a very early experimental stage, but we are working on a bootstrap script distributed through sneakernet, which can add servers to the network with one line of code.
More soon!
Love, SSC
Manual Setup
Prerequisites
- Docker and Docker Compose installed
- Access to Headscale/Tailscale network
- Headscale IP of your dusty orchestration server
Steps
- Clone this repository:
git clone https://git.secretserver.club/Secret-Server-Club/web-node.git
cd web-node
- Create
.envfile from example:
cp .env.example .env
- Edit
.envwith your configuration:
DUSTY_URL=http://100.64.0.2:5000 # Your dusty server's Headscale IP
HEADSCALE_IP= # This server's Headscale IP (auto-detected if empty)
SERVER_NAME=web-node # Your server's name
SERVER_LOCATION=None of your business # Server location
- Create data directory:
mkdir -p data
- Build and start the container:
docker-compose build
docker-compose up -d
- Check logs to verify registration:
docker-compose logs -f
Look for: ✅ Auto-registered with dusty! Headscale IP: 100.64.x.x
How It Works
- Dusty calls
/api/deploywith domain, git_repo, and git_branch - Web Node clones or updates the git repository to
/app/data/sites/{domain} - Web Node generates an nginx config for the domain in
/app/data/nginx/sites-available/{domain} - Web Node enables the nginx site (creates symlink in
/app/data/nginx/sites-enabled/) - Web Node reloads nginx
- The site is now accessible via nginx on port 80
Architecture
- Docker Container: Runs both nginx and Flask API via supervisord
- Data Volume:
./datadirectory is mounted to/app/datain container- Sites:
/app/data/sites/{domain} - Nginx configs:
/app/data/nginx/sites-available/andsites-enabled/ - Homepage:
/app/data/homepage/index.html
- Sites:
- Network: Uses external
tailscale0network for Headscale connectivity - Ports:
- 80: nginx (HTTP)
- 5000: Flask API
API Endpoints
POST /api/deploy
Deploy a site from a git repository.
Request:
{
"domain": "example.com",
"git_repo": "https://github.com/user/repo",
"git_branch": "main"
}
Response:
{
"status": "deployed",
"domain": "example.com",
"site_path": "/app/data/sites/example.com",
"deployed_at": "2024-01-01T00:00:00"
}
GET /api/info
Get server information (for dusty to discover).
Response:
{
"name": "web-node",
"location": "None of your business",
"headscale_ip": "100.x.x.x",
"sites_served": 5,
"status": "online"
}
GET /api/health
Health check endpoint.
Response:
{
"status": "healthy",
"nginx": "active",
"sites_served": 5,
"timestamp": "2024-01-01T00:00:00"
}
GET /api/sites
List all deployed sites.
Response:
[
{
"domain": "example.com",
"path": "/app/data/sites/example.com",
"exists": true
}
]
GET /api/homepage
Get server homepage HTML.
PUT /api/homepage
Update server homepage HTML.
Request:
{
"html": "<html>...</html>"
}
Nginx Configuration
- Base nginx config:
/etc/nginx/nginx.conf(copied into container) - Site configs:
/app/data/nginx/sites-available/{domain} - Enabled sites:
/app/data/nginx/sites-enabled/{domain}(symlinks) - Nginx includes all configs from
sites-enabled/*automatically
Each deployed site gets its own nginx config that serves from /app/data/sites/{domain} and listens on port 80 for the domain name.
Auto-Registration
Web Node automatically registers itself with dusty on startup. It:
- Detects its Headscale IP (from
HEADSCALE_IPenv var, or auto-detects) - Sends registration request to
DUSTY_URL/api/servers/register - Includes server name, location, and Headscale IP
If auto-registration fails, check:
DUSTY_URLis correct and dusty is reachableHEADSCALE_IPis set correctly (or can be auto-detected)- Container logs:
docker-compose logs
Notes
- Sites are served directly from the git repository directory
- If a site already exists, it will be updated via
git pull - Nginx configs are auto-generated and managed by the API
- No sudo access needed - everything runs in Docker container
- Data persists in
./datadirectory on host - Multiple sites share one nginx instance (more efficient than per-site containers)