---
name: vault-browser
description: "Serve the Syncthing-mirrored Obsidian vault over HTTPS when MEDIA: prefix delivery fails on Windows clients. Covers the full stack: Python HTTP server, nginx reverse proxy, systemd unit, DNS subdomain, and Let's Encrypt cert reuse."
---

# Vault Browser

## Trigger
Use this skill when:
- The user is on Windows (Connie) and `.html` files download instead of opening when delivered via `MEDIA:`
- The user requests a clickable URL for vault content
- You need to serve files from `/root/.hermes/vault/` over HTTP/HTTPS

Do NOT use `MEDIA:` for `.html` files on Windows — it forces a download. Use a URL instead.

## Architecture
```
vault.robblake.cloud (HTTPS)
    ↓ nginx proxy
127.0.0.1:9124 (Python HTTP server)
    ↓ serves
/root/.hermes/vault/
```

## Setup steps

### 1. DNS
Add an A record in Hostinger hPanel:
- **Hostname:** `vault`
- **Value:** `2.25.172.164`
- **TTL:** `300`

Verify propagation:
```bash
dig +short vault.robblake.cloud @8.8.8.8
```

### 2. Python HTTP server (systemd unit)
Create `/etc/systemd/system/vault-browser.service`:

```ini
[Unit]
Description=Vault Browser (Python HTTP Server)
After=network.target

[Service]
Type=simple
WorkingDirectory=/root/.hermes
ExecStart=/usr/bin/python3 -m http.server 9124 --bind 0.0.0.0
Restart=always
RestartSec=5
Environment=HOME=/root

[Install]
WantedBy=multi-user.target
```

Enable and start:
```bash
systemctl daemon-reload
systemctl enable vault-browser.service
systemctl start vault-browser.service
```

Verify:
```bash
systemctl status vault-browser.service --no-pager
ss -tlnp | grep 9124
curl -s http://127.0.0.1:9124/index.html | head -5
```

### 3. nginx reverse proxy
Create `/etc/nginx/sites-available/vault.robblake.cloud`:

```nginx
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name vault.robblake.cloud;

    ssl_certificate /etc/letsencrypt/live/vault.robblake.cloud/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/vault.robblake.cloud/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    location / {
        proxy_pass http://127.0.0.1:9124;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

server {
    listen 80;
    listen [::]:80;
    server_name vault.robblake.cloud;
    return 301 https://$host$request_uri;
}
```

Enable and reload:
```bash
ln -sf /etc/nginx/sites-available/vault.robblake.cloud /etc/nginx/sites-enabled/vault-browser.service
nginx -t && systemctl reload nginx
```

### 4. Verify end-to-end
```bash
curl -sk https://vault.robblake.cloud/index.html | head -10
curl -sk https://vault.robblake.cloud/Projects/Active/motion-canvases/premium-line-bail-bonds.html | head -5
```

## Pitfalls

### /root permissions
`/root` is `700` (root-only). nginx runs as `www-data` and cannot traverse `/root/.hermes/vault/` regardless of inner file permissions, ACLs, or bind mounts to `/var/www/`. The Python server runs as root via systemd, so it can read the files. nginx only needs to proxy to `127.0.0.1:9124` — it never touches `/root` directly.

### nginx conflicting server_name warnings
If `nginx -t` shows `conflicting server_name "robblake.cloud"` warnings, check for duplicate server blocks. Run:
```bash
nginx -T | grep "server_name"
ls -la /etc/nginx/sites-enabled/
```
Remove duplicates and reload. The warnings don't break functionality but indicate config drift.

### Port conflicts
If `vault-browser.service` fails with `Address already in use`, kill the stale process:
```bash
kill $(pgrep -f "http.server 9124")
systemctl start vault-browser.service
```

### Address already in use on boot
If the service fails on boot because the old PID file persists, add `PIDFile=/run/vault-browser.pid` to the `[Service]` section, or increase `RestartSec`.

## URL formats for delivery
Once live, deliver URLs to the user:

**HTTPS (preferred):**
```
https://vault.robblake.cloud/index.html
https://vault.robblake.cloud/Projects/Active/motion-canvases/premium-line-bail-bonds.html
```

**HTTP fallback:**
```
http://2.25.172.164:9124/index.html
```

**MEDIA: prefix (Windows last resort only):**
```
MEDIA:/root/.hermes/vault/Projects/Active/motion-canvases/premium-line-bail-bonds.html
```

## Maintenance
- The systemd unit auto-restarts on failure and survives reboots
- Files are served directly from `/root/.hermes/vault/` — no sync needed
- Syncthing continues to mirror vault content to Connie + Obsidian independently
