# Hosting ravipotineni.com from a Windows Laptop

This document captures how this site is hosted, why each piece exists, and the
problems that came up during initial setup (so future-you knows what to look
for if something breaks).

## Architecture

```
  Visitor's browser
        |
        v  HTTPS (auto TLS from Cloudflare)
  +-----------------+
  |   Cloudflare    |   DNS for ravipotineni.com points here
  |     edge        |   (CNAME -> <tunnel-uuid>.cfargotunnel.com)
  +-----------------+
        |
        |  encrypted tunnel (outbound from laptop, no inbound port open)
        v
  +-----------------+
  |   cloudflared   |   Runs on the laptop, maintains 4 outbound
  |    (Windows)    |   connections to Cloudflare edge.
  +-----------------+
        |
        v  http://localhost:8080  (Host header rewritten to "localhost")
  +-----------------+
  |    serve.ps1    |   Pure PowerShell static file server
  |  (HttpListener) |   Serves index.html / styles.css / script.js
  +-----------------+
```

### Why Cloudflare Tunnel instead of port forwarding

- **No inbound ports opened on the home router** — better security, and most
  consumer ISPs block ports 80/443 anyway.
- **No static IP needed** — the tunnel is an outbound connection from the
  laptop to Cloudflare, so a dynamic home IP is fine.
- **Free auto HTTPS** — Cloudflare terminates TLS at the edge; the laptop only
  serves plain HTTP on localhost.
- **Home IP stays hidden** — visitors only ever see Cloudflare's IPs.

## Files in this folder

| File | Purpose |
|---|---|
| `index.html` | The actual page |
| `styles.css` | Dark theme, mobile-responsive |
| `script.js` | Small JS (currently just sets the footer year) |
| `serve.ps1` | Local web server on port 8080 (no dependencies) |
| `setup-tunnel.ps1` | One-shot script to create the Cloudflare tunnel and write its config |
| `SETUP.md` | This file |

The cloudflared config itself lives at `%USERPROFILE%\.cloudflared\config.yml`,
not in this folder.

## First-time setup (what was actually done)

### 1. Install cloudflared

```powershell
winget install --id Cloudflare.cloudflared
```

Close and reopen PowerShell so `cloudflared` lands on PATH.

### 2. Authenticate with Cloudflare

```powershell
cloudflared tunnel login
```

Browser opens, pick `ravipotineni.com`, authorize. A cert is saved to
`%USERPROFILE%\.cloudflared\cert.pem`.

### 3. Run the setup script

```powershell
cd c:\RAVI_T\WEBSITE
.\setup-tunnel.ps1
```

This creates a tunnel named `mysite`, writes `config.yml`, and creates the DNS
records in Cloudflare for `ravipotineni.com` and `www.ravipotineni.com`.

### 4. Run the site

Two PowerShell windows:

```powershell
# Window 1
cd c:\RAVI_T\WEBSITE
.\serve.ps1
```

```powershell
# Window 2
cloudflared tunnel run mysite
```

Visit `https://ravipotineni.com`.

## Day-to-day operation

- **The site is only up while both processes are running.** Close either
  PowerShell window and the site goes down.
- **The laptop must be awake.** Sleep/hibernate = downtime. Consider disabling
  sleep when on AC power if uptime matters.
- **Editing content:** just edit `index.html`, `styles.css`, or `script.js`.
  Changes are live immediately on next browser refresh — no rebuild step.

## Challenges encountered (and how each was fixed)

### Challenge 1: PowerShell blocked the scripts

**Symptom:**
```
.\setup-tunnel.ps1 : File ... cannot be loaded because running scripts is
disabled on this system.
```

**Cause:** Windows defaults the PowerShell execution policy to `Restricted`,
which blocks all local scripts.

**Fix:** Allow locally-authored scripts to run (per-user, no admin needed):
```powershell
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
```
`RemoteSigned` permits scripts you wrote locally but still blocks unsigned
scripts downloaded from the internet — the safe middle ground.

### Challenge 2: PowerShell couldn't parse the setup script

**Symptom:** A cascade of errors that looked unrelated — "Missing type name
after '['", "Unexpected token 'hostname:'", "Missing closing ')'", etc.

The clue was buried in one of them: `(DNS may already exist â€" continuing)`.
That `â€"` is what an em-dash (`—`) looks like when the bytes are written as
UTF-8 but read as Windows-1252.

**Cause:** Windows PowerShell 5.1 reads `.ps1` files as Windows-1252 by
default. The file was written as UTF-8 (no BOM). Every em-dash became three
garbage characters — one of which was a stray `"`, which broke the here-string
quoting and made the parser misinterpret every following line.

**Fix:** Rewrote the script using ASCII only (plain hyphens instead of
em-dashes). Verified with PowerShell's parser API before re-running:
```powershell
$tokens = $null; $errors = $null
[System.Management.Automation.Language.Parser]::ParseFile(
  ".\setup-tunnel.ps1", [ref]$tokens, [ref]$errors
) | Out-Null
if ($errors.Count -eq 0) { "OK" } else { $errors }
```

**Lesson:** When writing `.ps1` files on Windows, stick to ASCII unless you
explicitly save the file as UTF-8 with BOM. The encoding mismatch produces
errors that look like syntax problems but are actually byte-interpretation
problems.

### Challenge 3: "Bad Request — Invalid Hostname" (HTTP 400)

**Symptom:** Tunnel connected fine, site loaded HTTPS at the edge, but the
browser displayed:
> Bad Request - Invalid Hostname
> HTTP Error 400. The request hostname is invalid.

**Cause:** This error message comes from **Windows HTTP.SYS**, not from
Cloudflare. The local server is a .NET `HttpListener` bound to
`http://localhost:8080/`. HTTP.SYS treats the `localhost` prefix as a Host
filter — it will only route requests whose `Host:` header is `localhost` to
that listener.

cloudflared, by default, forwards the original Host header. So requests
arrived at the laptop with `Host: ravipotineni.com`, HTTP.SYS saw a hostname
it had no binding for, and returned 400 before the listener ever saw the
request.

**Fix:** Told cloudflared to rewrite the Host header to `localhost` before
forwarding. Added `originRequest.httpHostHeader` to each ingress rule in
`%USERPROFILE%\.cloudflared\config.yml`:

```yaml
ingress:
  - hostname: ravipotineni.com
    service: http://localhost:8080
    originRequest:
      httpHostHeader: localhost
  - hostname: www.ravipotineni.com
    service: http://localhost:8080
    originRequest:
      httpHostHeader: localhost
  - service: http_status:404
```

Then restarted the tunnel (Ctrl+C, then `cloudflared tunnel run mysite`).

**Alternative fix** (not chosen): bind the HttpListener to a wildcard prefix
like `http://+:8080/`. That accepts any Host header but requires running the
script as Administrator or creating a `netsh http add urlacl` reservation —
more friction than the config-side fix.

**Lesson:** When you put a `localhost`-bound .NET `HttpListener` behind any
reverse proxy or tunnel, you almost always need the proxy to rewrite the
Host header to `localhost`, or you'll get HTTP.SYS 400s that *look* like a
Cloudflare problem.

## Troubleshooting cheat sheet

| Symptom | Likely cause | Fix |
|---|---|---|
| `Error 1033` (argo-tunnel error) on first visit | DNS not propagated yet | Wait 1-2 min |
| `Error 502 Bad Gateway` | Tunnel up but `serve.ps1` isn't running | Start `serve.ps1` |
| `400 Invalid Hostname` | `httpHostHeader: localhost` missing from config | See Challenge 3 |
| `This site can't be reached` | DNS still propagating, or cached | `ipconfig /flushdns` |
| Browser shows "Not secure" | Cloudflare proxy off for that record | Cloudflare dashboard -> DNS -> orange-cloud both records |
| Tunnel logs show no `Registered tunnel connection` | Laptop has no internet, or Cloudflare creds expired | Check network; re-run `cloudflared tunnel login` if needed |

## Future improvements (not done yet)

- **Auto-start on boot.** Right now both processes need to be started manually
  after every reboot. Two pieces:
  1. `cloudflared service install` (run from elevated PowerShell) makes the
     tunnel a Windows service.
  2. Register `serve.ps1` with Task Scheduler to run at logon / system start.
- **Prevent sleep on AC power** so the site stays up overnight:
  `powercfg /change standby-timeout-ac 0`
- **Move to a real static-site generator** (Astro, 11ty, Hugo) once the site
  outgrows hand-written HTML.
- **Replace `serve.ps1` with a more standard server** (e.g. Caddy or nginx)
  once there's more than one page or any dynamic behavior.
