webcentral 2.1.5

A reverse proxy that runs multiple web applications on a single server with on-demand startup, sandboxing, auto-reload, and Let's Encrypt certificates
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# Webcentral

A reverse proxy that runs multiple web applications for multiple users on a single server. Just put your app in a directory named like the target domain (eg `myapp.example.com/`), point DNS at the server, and you're done! The app will start (and shutdown) on-demand, and reload when its files change.


## Features

### Per domain request handling
- Run an executable (that should start serving on $PORT) either from a Docker image or in a Firejail sandbox
- Port-forward, HTTP-redirect, HTTP-proxy or static-serve requests
- Config file not always needed (detects `Procfile`, `package.json`, `public/`)

### Application lifecycle
- On-demand startup when first accessed
- Zero-downtime application restarts triggered by file changes
- Automatic shutdown after configurable idle period
- Daily log per application files with automatic pruning

### HTTPS & routing
- Let's Encrypt certificates acquired and renewed automatically
- Configurable HTTP ↔ HTTPS and www redirects
- Transparent WebSocket proxying

### Multi-user & isolation
- When started as root, all local users can host applications (run with their own permissions)
- Firejail or Docker sandboxing
- Each application has its own decentralized configuration

**Security Notice:** While Firejail and Docker add sandboxing, the integration hasn't been thoroughly audited. Webcentral may introduce additional attack surface. Use appropriate caution.

---

## Quick Start

```sh
# Install dependencies
sudo apt install git firejail docker.io rustc

# Build
git clone https://github.com/vanviegen/webcentral.git
cd webcentral
cargo build --release

# Run
sudo ./target/release/webcentral --email you@example.com
```

The `email` flag is mandatory, as it's needed for Let’s Encrypt. Alternatively you can disable HTTPS (` ./target/release/webcentral -https 0`). See `./target/release/webcentral --help` for more options.

Create a directory at `~/webcentral-projects/someapp.yourdomain.com/` with either:
- A `Procfile` for Heroku-style applications
- A `package.json` for Node.js apps
- A `public/` folder for static files
- A `webcentral.ini` for custom configuration

Point DNS for `someapp.yourdomain.com` at your server. Up and running!

---

## Comparison with Alternatives

| Feature | Webcentral | Caddy | Traefik | Nginx | Dokku | Coolify |
|---------|------------|-------|---------|-------|-------|-------|
| Auto HTTPS (Let's Encrypt) |||| Manual | ✓ (plugin) ||
| Zero-config static sites |||||||
| On-demand app startup |||||||
| Auto-reload on file change |||||| ✓ (git push) |
| Idle shutdown |||||||
| Multi-user (shared port 80/443) |||||||
| Built-in sandboxing | Docker or Firejail || Docker || Docker | Docker |
| Config complexity | Minimal | Low | Medium | High | Medium | Medium |
| Container orchestration |||||||

**Caddy/Nginx/Traefik** are pure reverse proxies—they route traffic but don't manage application lifecycles. You need separate tools (systemd, Docker Compose, Kubernetes) to run your apps.

**Dokku/Coolify** are self-hosted PaaS platforms with git-push deployment, but require more setup and resources. They're better suited for team environments with CI/CD pipelines.

**Webcentral** fills the gap for developers who want to quickly host multiple small apps/sites on a single VPS without container orchestration overhead. Just drop files in a folder and go.

---

## Non-root vs root usage

When run as a regular user, by default Webcentral searches `~/webcentral-projects/` for project directories. When run as root, it searches all users' `webcentral-projects` directories by default and runs each project with its owner's permissions. This allows multiple users to share the precious ports 80 and 443, without having to give them privileged access to the server.

If you want to run WebCentral as a regular user while still being able to bind to privileged ports, run `sudo setcap 'cap_net_bind_service=+ep' ./target/release/webcentral` once.

---

## Request Handling

Projects are automatically detected based on their contents:

### 1. Firejailed Command

**Trigger:** `webcentral.ini` with `command` property (without `[docker]` section)

Runs a server process in a Firejail sandbox. The process should start an HTTP server on `$PORT`.

**Firejail sandboxing:**
- Read-only access to system directories (`/bin`, `/usr`)
- No access to home directories or other user files
- Faster startup, lower memory usage

**Example:**
```ini
command = php -S 0.0.0.0:$PORT -file test.php
```

**Worker processes:**

You can run background worker processes alongside the main command:

```ini
command = python app.py --port $PORT
worker = python background_tasks.py
worker:email = python email_processor.py
```

Use `worker` for a single unnamed worker, or `worker:name` for multiple named workers. Workers share the same lifecycle as the main process and have access to the same environment variables.

### 2. Dockerized Command

**Trigger:** `webcentral.ini` with `command` property and `[docker]` section

Runs a server process in a Docker container. The process should start an HTTP server on `$PORT` (defaults to 8000).

**Docker containerization:**
- Completely isolated environment
- Higher memory usage, slower startup
- Runs as the project owner (uid/gid passed via `--user` flag)
- Automatically mounts `/etc/passwd` and `/etc/group` for user resolution
- More configuration options

**Example:**
```ini
command = php -S 0.0.0.0:$PORT -file test.php
[docker]
base = debian
packages[] = php
packages[] = composer
commands[] = composer install
```

**Docker Configuration Options:**
- `base` - Base Docker image (default: `alpine`)
- `commands` - Build commands (strings or arrays) - run during image build
- `packages` - Packages to install (auto-detects `apk`, `apt-get`, `dnf`, or `yum`)
- `mounts` - Persistent directories (stored in `_webcentral_data/mounts/<path>`, owned by project user)
  - Relative paths (e.g., `data`) are mounted relative to `app_dir`
  - Absolute paths (e.g., `/var/lib/data`) are mounted at that exact location in container
- `http_port` - Container HTTP port (default: 8000)
- `app_dir` - Mount point for project directory (default: `/app`)
- `mount_app_dir` - Set to `false` to skip mounting project directory and to run as root instead of your user (default: `true`)

**Volume mounts:**
- Project directory is mounted at `app_dir` (if `mount_app_dir` is not `false`)
- Home directory is mounted at `_webcentral_data/home` (if `mount_app_dir` is `true`)
- Custom mounts are created in `_webcentral_data/mounts/` with correct ownership

**Real-world example (Trilium Notes):**
```ini
command = node /usr/src/app/src/www
[docker]
base = zadam/trilium:0.47.6
http_port = 8080
```

**Example with persistent data:**
```ini
command = ./server
[docker]
base = alpine
packages = nodejs npm
mounts[] = data              ; Mounted at /app/data
mounts[] = /var/cache/app    ; Mounted at /var/cache/app
```

### 3. Forward

**Trigger:** `webcentral.ini` with `port` or `socket_path` property

Forwards requests to a local port or UNIX socket without modifying the `Host:` header.

```ini
port = 3000
host = 192.168.10.20
```

Or:
```ini
socket_path = /my/path/test.socket
```

### 4. Redirect

**Trigger:** `webcentral.ini` with `redirect` property

Returns HTTP 301 redirect to the specified URL plus the request path and query string.

```ini
redirect = https://new-service-name.example.com
```

### 5. Proxy

**Trigger:** `webcentral.ini` with `proxy` property

**(Experimental!)** Proxies requests to a remote URL with header rewriting (unlike Forward).

```ini
proxy = https://www.google.com
```

### 6. Procfile Application

**Trigger:** `Procfile` exists (no `webcentral.ini` needed)

Runs applications using Heroku's Procfile format. The `web` process should start an HTTP server on `$PORT`.

**Supported process types:**
- `web` - Main HTTP server process (required)
- `worker` - Background worker process (optional, multiple allowed)
- `urgentworker` - Same as worker (alias)

**Unsupported process types** (will be logged and ignored):
- `release`, `console`, and other custom types

**Example Procfile:**
```
web: python app.py --port $PORT
worker: python background_tasks.py
worker: python email_processor.py
```

**Notes:**
- All processes share the same environment variables
- Workers start after the web process is ready
- Workers are stopped when the application stops
- All processes run in the same sandbox (Firejail or Docker)

### 7. Node.js Application

**Trigger:** `package.json` exists (no `webcentral.ini` or `Procfile` needed)

Automatically runs `npm start`, which should start an HTTP server on `process.env.PORT`.

### 8. Static Files

**Trigger:** `public/` directory exists (no `webcentral.ini` needed)

Serves files from the `public/` directory.

---

## Configuration

### Auto-Reload

Applications automatically reload when:

1. **Files change** - Watches for changes in the project directory
2. **After inactivity** - Default 5 minutes of no requests

**Default exclusions** (not watched for changes):
- `_webcentral_data`, `data`, `log`, `logs`, `home`
- `node_modules`
- `**/*.log`
- `**/.*` (hidden files)

**Custom reload configuration:**
```ini
command = ./start.sh --production
[reload]
timeout = 0                ; Disable inactivity shutdown (seconds)
include[] = src            ; Only watch src/ directory
include[] = config.yaml    ; And this file
exclude[] = src/build      ; Ignore build directory
exclude[] = **/*.bak       ; Ignore .bak files
```

Note: `webcentral.ini` is always watched, and `_webcentral_data` is always excluded.

### URL Rewrites

**(Experimental!)** Rewrite request paths using regular expressions.

```ini
[rewrite]
/blog/(.*?)/.* = /articles/$1.html              ; Simplify URLs
/favicon.ico = /favicon.ico                     ; Passthrough
/[^/]* = /index.html                            ; Catch-all to index.html
```

Rules are applied in order. First match wins. Use `$1`, `$2`, etc. for captures.

### Environment Variables

Set environment variables for your application:

```ini
[docker]
base = bitwardenrs/server:alpine
mounts[] = data
mounts[] = web-vault
[environment]
ROCKET_PORT = 8000
WEB_VAULT_ENABLED = true
```

### HTTP/HTTPS Redirects

Control protocol redirects per-project:

```ini
redirect_http = false      ; Don't redirect HTTP to HTTPS
redirect_https = true      ; Redirect HTTPS to HTTP
```

Defaults: `redirect_http = true`, `redirect_https = false` (configurable via `--redirect-http`)

### Request Logging

Enable per-project request logging:

```ini
log_requests = true
```

---

## Command-Line Options

| Option | Description |
|--------|-------------|
| `--email=EMAIL` | Email for Let's Encrypt. Required unless `--https=0`. |
| `--projects=DIR` | Project directory glob. Default: `/home/*/webcentral-projects` (root) or `$HOME/webcentral-projects` (user). |
| `--config=DIR` | Config storage directory. Default: `/var/lib/webcentral` (root) or `$HOME/.webcentral` (user). |
| `--https=PORT` | HTTPS port. Default: `443`. Set to `0` to disable. |
| `--http=PORT` | HTTP port. Default: `80`. Set to `0` to disable. |
| `--redirect-http=BOOL` | Redirect HTTP to HTTPS. Default: `true`. |
| `--redirect-www=BOOL` | Auto-redirect between `example.com` and `www.example.com`. Default: `true`. |
| `--firejail=BOOL` | Enable Firejail sandboxing. Default: `true`. (Disabling risks security and process leaks.) |
| `--prune-logs=DAYS` | Days to keep log files. Default: `28`. Set to `0` to disable pruning. |
| `--acme-url=URL` | ACME directory URL. Default: Let's Encrypt (`https://acme-v02.api.letsencrypt.org/directory`). |
| `--acme-version=VER` | ACME protocol version. Default: `draft-11`. |

---

## Log Files

Application output is written to `_webcentral_data/log/<DATE>.log` in each project directory. Logs rotate daily and are automatically pruned after 28 days (configurable via `--prune-logs`).

---

## Running with systemd

Create `/etc/systemd/system/webcentral.service`:

```ini
[Service]
ExecStart=/usr/local/bin/webcentral --email YOUR-EMAIL-ADDRESS
Restart=always

[Install]
WantedBy=multi-user.target
```

Start the service:

```sh
sudo systemctl daemon-reload
sudo systemctl start webcentral
sudo systemctl enable webcentral  # Start on boot
```

Check status:

```sh
sudo systemctl status webcentral -n 20
```

Make sure no other services are using ports 80 or 443.

---

## Changelog

2025-12-10 (2.1.4):
 - Static file server now sends MIME types based on file extensions

2025-12-08 (2.1.3):
 - Fix config reload on file change (was reusing stale config)
 - Simplified process lifecycle: new Project replaces old, waits for predecessor to stop

2025-12-08 (2.1.2):
 - Await process shutdown before restarting
 - More robust process lifecycle management

2025-12-02 (2.1.1):
 - Keep bindings.json up-to-date when domains are added/removed
 - Code reduction

2025-11-27 (2.1.0):
 - Fix for unnecessary inotify watchers
 - Docker configurations without custom RUN commands or packages don't use a custom build anymore
 - Use Podman (preferred) it it's installed
 - No more Docker user mapping - root inside the container for compatibility
 - Exit immediately if ports cannot be bound

2025-11-26 (2.0.0):
 - Initial AI-driven Rust reimplementation of the [original Node.js version]https://github.com/vanviegen/webcentral/tree/nodejs. It was born out of Node.js dependency rot frustration. It also adds multi-threading, and should be fully compatible with original configuration format and project structure.
 - Added a test suite, mostly for catching configuration-change race conditions.
 - Configurable log retention (`--prune-logs`)
 - Proactive certificate acquisition for newly created projects (no longer awaiting the first request)
 - Added Procfile support (though no `release:` yet)
 - Added support for worker processes alongside main app process (not for Docker yet)

See `git log` for further changes.

2018-09-14:
  - Initial release.

---

## License

ISC