2ec6f73843
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>
217 lines
5.4 KiB
Markdown
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) | |