| 1 | diff --git a/deploy/edge/Caddyfile b/deploy/edge/Caddyfile |
| 2 | new file mode 100644 |
| 3 | index 0000000..e0d915d |
| 4 | --- /dev/null |
| 5 | +++ b/deploy/edge/Caddyfile |
| 6 | @@ -0,0 +1,18 @@ |
| 7 | +{ |
| 8 | + auto_https off |
| 9 | + admin off |
| 10 | + servers { |
| 11 | + enable_full_duplex |
| 12 | + } |
| 13 | +} |
| 14 | + |
| 15 | +# Fly's proxy terminates TLS and forwards HTTP here with the original Host |
| 16 | +# and X-Forwarded-Proto. Upstream traffic reaches the relay host over the |
| 17 | +# tailnet through tailscaled's local CONNECT proxy. |
| 18 | +:8080 { |
| 19 | + reverse_proxy http://{$RELAY_TAILNET_HOST}:8080 { |
| 20 | + transport http { |
| 21 | + network_proxy http://127.0.0.1:1055 |
| 22 | + } |
| 23 | + } |
| 24 | +} |
| 25 | diff --git a/deploy/edge/Dockerfile b/deploy/edge/Dockerfile |
| 26 | new file mode 100644 |
| 27 | index 0000000..02d6720 |
| 28 | --- /dev/null |
| 29 | +++ b/deploy/edge/Dockerfile |
| 30 | @@ -0,0 +1,8 @@ |
| 31 | +# Edge proxy for a tiny relay: Fly terminates TLS, this machine joins the |
| 32 | +# tailnet with userspace networking and forwards plain HTTP to the relay host. |
| 33 | +FROM caddy:2-alpine |
| 34 | +RUN apk add --no-cache tailscale ca-certificates |
| 35 | +COPY Caddyfile /etc/caddy/Caddyfile |
| 36 | +COPY start.sh /start.sh |
| 37 | +RUN chmod +x /start.sh |
| 38 | +ENTRYPOINT ["/start.sh"] |
| 39 | diff --git a/deploy/edge/fly.toml b/deploy/edge/fly.toml |
| 40 | new file mode 100644 |
| 41 | index 0000000..ecd81bd |
| 42 | --- /dev/null |
| 43 | +++ b/deploy/edge/fly.toml |
| 44 | @@ -0,0 +1,38 @@ |
| 45 | +app = "tiny-edge" |
| 46 | +primary_region = "dfw" |
| 47 | + |
| 48 | +[build] |
| 49 | + |
| 50 | +[env] |
| 51 | + TS_HOSTNAME = "tiny-edge" |
| 52 | + TS_STATE_DIR = "/var/lib/tailscale" |
| 53 | + RELAY_TAILNET_HOST = "100.87.95.99" |
| 54 | + |
| 55 | +[mounts] |
| 56 | + source = "tailscale_state" |
| 57 | + destination = "/var/lib/tailscale" |
| 58 | + |
| 59 | +[[services]] |
| 60 | + internal_port = 8080 |
| 61 | + protocol = "tcp" |
| 62 | + auto_stop_machines = false |
| 63 | + auto_start_machines = true |
| 64 | + min_machines_running = 1 |
| 65 | + |
| 66 | + [[services.ports]] |
| 67 | + port = 80 |
| 68 | + handlers = ["http"] |
| 69 | + force_https = true |
| 70 | + |
| 71 | + [[services.ports]] |
| 72 | + port = 443 |
| 73 | + handlers = ["tls", "http"] |
| 74 | + |
| 75 | + [services.concurrency] |
| 76 | + type = "connections" |
| 77 | + hard_limit = 2000 |
| 78 | + soft_limit = 1000 |
| 79 | + |
| 80 | +[[vm]] |
| 81 | + size = "shared-cpu-1x" |
| 82 | + memory = "256mb" |
| 83 | diff --git a/deploy/edge/slate.Caddyfile.snippet b/deploy/edge/slate.Caddyfile.snippet |
| 84 | new file mode 100644 |
| 85 | index 0000000..d385e25 |
| 86 | --- /dev/null |
| 87 | +++ b/deploy/edge/slate.Caddyfile.snippet |
| 88 | @@ -0,0 +1,14 @@ |
| 89 | +# Add to the relay host's Caddyfile. The edge machine reaches this listener |
| 90 | +# over the tailnet only; the port is not published anywhere else. |
| 91 | +http://012.run:8080, http://*.012.run:8080 { |
| 92 | + reverse_proxy 127.0.0.1:7447 |
| 93 | + @signerQR path /qr.svg /r/*/qr.svg |
| 94 | + log_skip @signerQR |
| 95 | + log { |
| 96 | + output file /logs/access.json { |
| 97 | + roll_size 10MiB |
| 98 | + roll_keep 7 |
| 99 | + } |
| 100 | + format json |
| 101 | + } |
| 102 | +} |
| 103 | diff --git a/deploy/edge/start.sh b/deploy/edge/start.sh |
| 104 | new file mode 100644 |
| 105 | index 0000000..ab1404e |
| 106 | --- /dev/null |
| 107 | +++ b/deploy/edge/start.sh |
| 108 | @@ -0,0 +1,18 @@ |
| 109 | +#!/bin/sh |
| 110 | +# Join the tailnet in userspace mode, expose an HTTP CONNECT proxy for Caddy, |
| 111 | +# then run Caddy. TS_AUTHKEY comes from a Fly secret and is only needed the |
| 112 | +# first time; afterwards the identity persists on the state volume. |
| 113 | +set -eu |
| 114 | +STATE="${TS_STATE_DIR:-/var/lib/tailscale}" |
| 115 | +mkdir -p /var/run/tailscale "$STATE" |
| 116 | +tailscaled --state="$STATE/tailscaled.state" --socket=/var/run/tailscale/tailscaled.sock \ |
| 117 | + --tun=userspace-networking --outbound-http-proxy-listen=127.0.0.1:1055 & |
| 118 | +until [ -S /var/run/tailscale/tailscaled.sock ]; do sleep 1; done |
| 119 | +if [ -n "${TS_AUTHKEY:-}" ]; then |
| 120 | + tailscale --socket=/var/run/tailscale/tailscaled.sock up --authkey="$TS_AUTHKEY" --hostname="${TS_HOSTNAME:-tiny-edge}" --accept-dns=false |
| 121 | +elif [ -f "$STATE/tailscaled.state" ]; then |
| 122 | + tailscale --socket=/var/run/tailscale/tailscaled.sock up --hostname="${TS_HOSTNAME:-tiny-edge}" --accept-dns=false |
| 123 | +else |
| 124 | + echo "TS_AUTHKEY is not set and no tailnet state exists; set the secret and redeploy" >&2 |
| 125 | +fi |
| 126 | +exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile |
| 127 | diff --git a/docs/personal-relay.md b/docs/personal-relay.md |
| 128 | index b33a769..9cb04f2 100644 |
| 129 | --- a/docs/personal-relay.md |
| 130 | +++ b/docs/personal-relay.md |
| 131 | @@ -50,6 +50,20 @@ sudo systemctl restart tiny |
| 132 | |
| 133 | The owner is `npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5`. Sign in with that identity to manage the relay. |
| 134 | |
| 135 | +## Public domain through a Fly edge |
| 136 | + |
| 137 | +The relay is also reachable at `https://012.run`, with sites on `*.012.run`. A small Fly machine, `deploy/edge`, terminates TLS at Fly's edge, joins the tailnet in userspace mode, and forwards plain HTTP over the tailnet to a listener on the relay host that only the tailnet can reach. Fly's proxy has no request body cap and passes WebSockets. |
| 138 | + |
| 139 | +```sh |
| 140 | +cd deploy/edge |
| 141 | +fly deploy |
| 142 | +fly logs --no-tail # first boot prints a Tailscale login link; open it to approve the machine |
| 143 | +fly certs check 012.run |
| 144 | +fly certs check "*.012.run" |
| 145 | +``` |
| 146 | + |
| 147 | +DNS at the registrar, all DNS-only records: A and AAAA for `012.run` and `*.012.run` pointing at the app's addresses from `fly ips list`, and a `_acme-challenge.012.run` CNAME from `fly certs setup "*.012.run"` for the wildcard certificate. The relay host's Caddyfile serves `012.run:8080` and `*.012.run:8080` over plain HTTP for the edge; see `deploy/edge/slate.Caddyfile.snippet`. After the certificates issue, set `--public-url https://012.run` on the relay so signed requests, NIP-11 and site hosts use the public name. The ts.net name keeps working through Funnel. |
| 148 | + |
| 149 | ## Repository hosting |
| 150 | |
| 151 | The relay hosts Git repositories over GRASP. Use [ngit](https://ngit.dev) so the owner signs the announcement, the ref state and each push with a remote signer. `ngit account login` shows a QR code to scan with Amber; the key stays on the phone. |