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>
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
# 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) |
|
||||
Reference in New Issue
Block a user