Web Terminals
Browser-based access to each LXC container’s tmux session, primarily for phone or remote machine access when SSH is not available.
Access
| URL | Container | Restriction |
|---|---|---|
| https://laptop.ipnoelp.com | Laptop Claude Code session | chas@omelasai.com only |
| https://term.ipnoelp.io/ | Landing page | All @omelasai.com |
| https://term.ipnoelp.io/chasclaude/ | chasclaude | chas@omelasai.com only |
| https://term.ipnoelp.io/infoclaude/ | infoclaude | chas@omelasai.com only |
| https://term.ipnoelp.io/seanclaude/ | seanclaude | All @omelasai.com |
| https://term.ipnoelp.io/jazclaude/ | jazclaude | All @omelasai.com |
| https://term.ipnoelp.io/managerclaude/ | managerclaude | All @omelasai.com |
Architecture
Browser
|
+-- Cloudflare Access (Google OAuth, @omelasai.com)
|
+-- nginx (:443, term.ipnoelp.io)
|
+-- Per-user restriction (Cf-Access-Authenticated-User-Email header)
|
+-- /chasclaude/ --> ttyd (127.0.0.1:7681) --> lxc-attach chasclaude
+-- /infoclaude/ --> ttyd (127.0.0.1:7682) --> lxc-attach infoclaude
+-- /seanclaude/ --> ttyd (127.0.0.1:7683) --> lxc-attach seanclaude
+-- /jazclaude/ --> ttyd (127.0.0.1:7684) --> lxc-attach jazclaude
+-- /managerclaude/ -> ttyd (127.0.0.1:7685) --> lxc-attach managerclaude
Components
ttyd
ttyd provides a web-based terminal. One ttyd instance per container, each running as a systemd service.
Each ttyd instance runs:
ttyd --port PORT --base-path /CONTAINER/ --writable \
lxc-attach -n CONTAINER -- su -l dev
This attaches directly to the container as the dev user with a full login shell, which triggers the .login-gateway.sh menu.
Systemd Services
| Service | Port | Container |
|---|---|---|
ttyd-chasclaude.service |
7681 | chasclaude |
ttyd-infoclaude.service |
7682 | infoclaude |
ttyd-seanclaude.service |
7683 | seanclaude |
ttyd-jazclaude.service |
7684 | jazclaude |
ttyd-managerclaude.service |
7685 | managerclaude |
All services bind to 127.0.0.1 only (not externally accessible). nginx handles external traffic.
nginx Configuration
File: /etc/nginx/sites-available/web-terminals
Key features:
- SSL with self-signed cert (Cloudflare terminates public SSL)
- WebSocket support (
UpgradeandConnectionheaders proxied) - Landing page at
/listing all containers - Per-container location blocks with proxy pass to ttyd
- chasclaude and infoclaude restricted to
chas@omelasai.comvia$http_cf_access_authenticated_user_emailheader
Cloudflare Access
A Cloudflare Access application protects term.ipnoelp.io:
- Type: Self-hosted
- Auth: Google OAuth
- Policy: Allow @omelasai.com email domain
- Session: 30-day cache
The Cf-Access-Authenticated-User-Email header is set by Cloudflare and checked by nginx for per-container access control.
Authentication Flow
- User opens
https://term.ipnoelp.io/chasclaude/ - Cloudflare Access intercepts (if no valid session)
- User authenticates via Google OAuth (@omelasai.com)
- Cloudflare sets session cookie +
Cf-Access-Authenticated-User-Emailheader - nginx checks email header for restricted containers
- If allowed, nginx proxies to the appropriate ttyd instance
- ttyd serves a web terminal attached to the container
Managing Web Terminals
# Check service status
ssh ovh5 "systemctl status ttyd-chasclaude ttyd-infoclaude ttyd-seanclaude ttyd-jazclaude ttyd-managerclaude"
# Restart a specific terminal
ssh ovh5 "sudo systemctl restart ttyd-chasclaude"
# View logs
ssh ovh5 "journalctl -u ttyd-chasclaude -n 20"
# Check nginx config
ssh ovh5 "sudo nginx -t"
ssh ovh5 "sudo systemctl reload nginx"
Files on Server
| File | Purpose |
|---|---|
/etc/systemd/system/ttyd-*.service |
Per-container ttyd services |
/etc/nginx/sites-available/web-terminals |
nginx reverse proxy config |
/etc/ssl/dashboard/cert.pem |
Self-signed SSL cert (shared with dashboard) |
/etc/ssl/dashboard/key.pem |
SSL private key |