Files
muxplex/cloudflare-tunnel-setup.md
Ken 2ec6f73843 feat: mobile UX polish, shell injection fix, and performance optimizations
This commit combines multiple improvements for mobile support, reliability, and performance:

Mobile & Accessibility Improvements:
- Fix touch scrolling on mobile by removing CSS overflow-y:hidden that blocked xterm.js native scroll
- Add beforeinput handler to prevent Android IME double-space-to-period duplication
- Implement mobile control character toolbar (Esc, Tab, Ctrl/Alt toggles, arrows, special chars)
- One-shot modifier toggles bring soft keyboard on demand without showing it for other keys
- Replace 25+ unicode/HTML entity icons with inline SVGs across app for better rendering

Security & Reliability:
- Fix shell injection vulnerability: session names with spaces/special chars now properly quoted
  - Use shlex.quote() in create_session and delete_session endpoints
  - URL-encode session names in bell hook to prevent malformed curl URLs
  - Also hardens against command injection through crafted session names

Performance Optimizations:
- Eliminate 2.4-5.6 second session creation delay (was 3 compounding bottlenecks)
  - Frontend: check for new session immediately (was waiting 2s for interval tick)
  - Backend: eagerly refresh session cache after tmux creation (was waiting for next poll)
  - Connection: poll for ttyd readiness instead of blind 0.8s sleep
- Typical new session now appears in UI in 200-400ms vs previous 4-5 seconds

Infrastructure:
- Improve ttyd port detection with multi-tool fallback (lsof → fuser → ss)
- Add Cloudflare tunnel setup documentation

Test Updates:
- Update frontend tests to match new setTimeout recursion pattern (replaces setInterval)

All 1306 tests pass.

Generated with Amplifier
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
2026-05-27 01:01:55 +00:00

217 lines
5.4 KiB
Markdown

# Cloudflare Tunnel Setup for Incus Host
## Overview
Single routing point for all services across Incus instances.
Services self-declare by dropping a file into a shared directory.
No port forwarding, no OPNsense reverse proxy config, no DDNS.
## Prerequisites
- Domain: ampbox.io (registrar: Namecheap)
- Cloudflare account (free tier) with ampbox.io DNS delegated
- At Namecheap: set nameservers to the ones Cloudflare assigns
- Verify domain is active in Cloudflare dashboard
## 1. Install cloudflared on the Incus host
```bash
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
| sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] \
https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install -y cloudflared
```
## 2. Authenticate and create the tunnel
```bash
cloudflared tunnel login # opens browser, authorize ampbox.io
cloudflared tunnel create ampbox # creates tunnel, saves credentials JSON
cloudflared tunnel route dns ampbox "*.ampbox.io" # wildcard DNS route
```
Note the credentials file path printed (typically
`~/.cloudflared/<TUNNEL_ID>.json`). You need it for step 4.
## 3. Create the service declaration directory
```bash
sudo mkdir -p /etc/cloudflared/services.d
```
## 4. Write the main tunnel config
```bash
# Replace <TUNNEL_ID> with your actual tunnel ID from step 2
sudo tee /etc/cloudflared/config.yml << 'EOF'
tunnel: ampbox
credentials-file: /root/.cloudflared/<TUNNEL_ID>.json
ingress:
- service: http_status:404
EOF
```
## 5. Share the declaration directory into all instances
```bash
incus profile device add default svc-declare disk \
source=/etc/cloudflared/services.d \
path=/mnt/services
```
## 6. Helper scripts (on the host)
### /usr/local/bin/svc-rebuild
Reads all service declarations and rebuilds the cloudflared ingress config.
```bash
#!/usr/bin/env bash
set -euo pipefail
CONFIG="/etc/cloudflared/config.yml"
SERVICES_DIR="/etc/cloudflared/services.d"
TUNNEL_CREDS="/root/.cloudflared/<TUNNEL_ID>.json" # fix this
cat > "$CONFIG" <<HEADER
tunnel: ampbox
credentials-file: $TUNNEL_CREDS
ingress:
HEADER
for f in "$SERVICES_DIR"/*.yml; do
[ -f "$f" ] || continue
while IFS= read -r line; do
echo " $line" >> "$CONFIG"
done < "$f"
done
echo " - service: http_status:404" >> "$CONFIG"
sudo systemctl restart cloudflared
echo "Rebuilt with $(ls "$SERVICES_DIR"/*.yml 2>/dev/null | wc -l) service(s)"
```
```bash
sudo install -m 755 /dev/stdin /usr/local/bin/svc-rebuild < svc-rebuild.sh
```
### /usr/local/bin/svc-watch
Auto-rebuilds on file changes (optional but recommended).
```bash
#!/usr/bin/env bash
set -euo pipefail
SERVICES_DIR="/etc/cloudflared/services.d"
echo "Watching $SERVICES_DIR for changes..."
while inotifywait -q -e create,delete,modify "$SERVICES_DIR"; do
sleep 1 # debounce
/usr/local/bin/svc-rebuild
done
```
```bash
sudo apt install -y inotify-tools
sudo install -m 755 /dev/stdin /usr/local/bin/svc-watch < svc-watch.sh
```
## 7. Systemd services (on the host)
### /etc/systemd/system/cloudflared.service
```ini
[Unit]
Description=Cloudflare Tunnel
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/cloudflared tunnel run ampbox
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
### /etc/systemd/system/svc-watch.service
```ini
[Unit]
Description=Service declaration watcher
After=cloudflared.service
[Service]
ExecStart=/usr/local/bin/svc-watch
Restart=always
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now cloudflared svc-watch
```
## 8. Declaring services FROM INSIDE an instance
### Expose an HTTP service
```bash
cat > /mnt/services/myapp.yml << EOF
- hostname: myapp.ampbox.io
service: http://$(hostname -I | awk '{print $1}'):8080
EOF
```
### Expose SSH
```bash
cat > /mnt/services/ssh-$(hostname).yml << EOF
- hostname: ssh-$(hostname).ampbox.io
service: ssh://$(hostname -I | awk '{print $1}'):22
EOF
```
### Remove a service
```bash
rm /mnt/services/myapp.yml
# svc-watch picks it up and rebuilds automatically
```
## 9. Client-side SSH config (on laptops / remote machines)
```
# ~/.ssh/config
Host *.ampbox.io
ProxyCommand cloudflared access ssh --hostname %h
```
Then: `ssh user@ssh-myinstance.ampbox.io` just works.
## 10. OPNsense cleanup
Once the tunnel is verified working, remove from OPNsense:
- All per-service reverse proxy rules in os-caddy
- Port forward / NAT rules for 80 and 443
- DDNS configuration (no longer needed)
OPNsense goes back to just being your firewall.
## Quick Reference
| Action | From inside instance |
|-------------------------|----------------------------------------------------------|
| Expose HTTP | `echo "- hostname: X.ampbox.io\n service: http://IP:PORT" > /mnt/services/X.yml` |
| Expose SSH | Same pattern with `service: ssh://IP:22` |
| Expose raw TCP | Same pattern with `service: tcp://IP:PORT` |
| Remove service | `rm /mnt/services/X.yml` |
| List registered | `ls /mnt/services/` |
| Check tunnel status | `sudo systemctl status cloudflared` (from host) |