# Tailscale Funnel + Caddy: Share Generated HTML & MD Files Publicly

**Last updated:** 2026-05-15 (Caddy upgrade from Python http.server)

## Architecture

```
User (phone/browser)                    Hermes Server
       │                                      │
       ▼  HTTPS (TLS)                         ▼
Tailscale Edge Node ──encrypted tunnel──▶ Caddy (:8080)
                                              │
                                    ┌─────────┼──────────┐
                                    ▼         ▼          ▼
                               /output/   /hermes/   /obsidian/
                              .html files .md skills  notes (synced)
```

## Setup

### 1. Install Caddy

```bash
sudo apt install caddy -y
```

### 2. Create Directory Structure

```bash
mkdir -p ~/files/{hermes,obsidian,projects,output}
```

Symlink Hermes directories:
```bash
ln -s ~/.hermes/skills   ~/files/hermes/skills
ln -s ~/.hermes/memories ~/files/hermes/memories
ln -s ~/.hermes/plans    ~/files/hermes/plans
```

### 3. Caddyfile (`~/files/Caddyfile`)

```caddy
:8080 {
    encode gzip
    file_server browse

    handle_path /hermes/* {
        root * /home/ubuntu/files/hermes
        file_server browse
    }
    handle_path /obsidian/* {
        root * /home/ubuntu/files/obsidian
        file_server browse
    }
    handle_path /projects/* {
        root * /home/ubuntu/files/projects
        file_server browse
    }
    handle_path /output/* {
        root * /home/ubuntu/files/output
        file_server browse
    }
    handle {
        root * /home/ubuntu/files
        file_server browse
    }
}
```

### 4. Start Caddy

```bash
HOME=/home/ubuntu caddy run --config ~/files/Caddyfile
```

For background persistence:
```bash
HOME=/home/ubuntu caddy run --config ~/files/Caddyfile &
# Or use caddy reload for hot-reload:
HOME=/home/ubuntu caddy reload --config ~/files/Caddyfile
```

### 5. Expose via Tailscale Funnel

```bash
tailscale funnel --bg 8080
# → https://<hostname>.<tailnet>.ts.net/
```

### 6. Done! Access URLs

```
https://<hostname>.<tailnet>.ts.net/              → File browser
https://<hostname>.<tailnet>.ts.net/output/       → Generated HTML reports
https://<hostname>.<tailnet>.ts.net/hermes/       → Skills/memories (MD files)
```

## File Sharing Convention

```
/output/report-name.html    ← Generated HTML reports
/output/architecture.html   ← Architecture diagrams
```

## Markdown Files (.md)

Caddy v2 does NOT have built-in Markdown rendering. `.md` files are served as raw `text/markdown`. For rendered viewing:
- **Upgrade path:** Install Pandoc + md2html middleware, or use a pre-render hook
- **For now:** The directory browsing + raw file access works — users can view source or download the `.md` file

## Stop / Tear Down

```bash
tailscale funnel reset   # Stop funnel
pkill -f "caddy run"     # Stop Caddy
```

## Security

- **No API keys in served files** — only HTML outputs and skill/memory MD files
- **Funnel is HTTPS-only** — encrypted end-to-end
- **No port forwarding needed** — doesn't touch cloud firewall
- **Revocable** — `tailscale funnel reset` kills exposure instantly
- **Only serve what's needed** — symlink only specific directories

## Pitfalls

- **Caddy needs $HOME set** — If run as root without HOME, set `HOME=/home/ubuntu`
- **Port 2019 conflict** — Caddy admin API defaults to :2019. If the system Caddy service is running, stop it first: `sudo systemctl stop caddy`
- **System Caddy service conflicts** — `apt install caddy` starts a system service on :80. Stop and disable it: `sudo systemctl stop caddy && sudo systemctl disable caddy`
- **handle_path vs handle** — use `handle_path` (strips the prefix) not `handle` (doesn't strip) when mapping `/path/* → different root`
- **Symlinks not followed by default** — Python's `Path.rglob` doesn't follow symlinks. Use `os.walk(followlinks=True)` at the application level
- **Caddyfile formatting** — Run `caddy fmt --overwrite Caddyfile` before reload to avoid warnings
- **Funnel URL stability** — The `https://<hostname>.<tailnet>.ts.net/` URL changes if you modify the tailnet name or hostname
