Skip to content

fredericosantos/glkvm-setup

Repository files navigation

glkvm-setup

Turn a GL.iNet GLKVM (or any always-on Tailscale node) into a wake gate for home servers. Visit a URL → server wakes up → you get redirected to it. No traffic is proxied — the GLKVM only handles the initial bounce.

Also provides valid TLS for the KVM web UI via Caddy + Cloudflare DNS challenge.

How it works

Client → on.{name}.example.com (or custom alias like watch.example.com)
       → Caddy (valid TLS, Cloudflare DNS challenge)
       → WoL gateway (Python, port 8888)
       → Server up + redirect set: 302 → server's own URL (direct connection)
       → Server up + no redirect:  "server is online" page
       → Server down:              WoL packet + waiting page (polls until up)

Port 443 is split by IP — Caddy binds to the Tailscale IP, nginx keeps the LAN + localhost for the KVM UI.

Quick start

1. Clone and configure

git clone <this-repo> ~/repos/glkvm-setup
cd ~/repos/glkvm-setup

# Create your server config from the template
cp servers.example.json servers.json
# Edit servers.json with your Tailscale IPs, MACs, domain, etc.

# Create .env with your Cloudflare API token and SSH target
echo "CLOUDFLARE_API_TOKEN=your-token-here" > .env
echo "GLKVM_SSH=root@glkvm" >> .env

To get a Cloudflare API token: Dashboard → My Profile → API Tokens → Create Token → Edit zone DNS template → scope to your domain zone.

2. Prepare the nginx config

Pull the existing KVM nginx config from your GLKVM:

scp root@<glkvm-ip>:/etc/kvmd/nginx-kvmd.conf .

Edit nginx-kvmd.conf — change the 443 listener from 0.0.0.0 to your specific IPs:

# Change this:
listen 443 ssl;
listen [::]:443 ssl;

# To this (use your GLKVM's LAN IP):
listen 192.168.1.x:443 ssl;
listen 127.0.0.1:443 ssl;

Also add server_name _; after the include /etc/kvmd/nginx/ssl.conf; line.

3. Install prerequisites (Mac, one-time)

brew install go
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest

4. Enable Tailscale SSH on the GLKVM

ssh root@<glkvm-ip>   # password required this one time
tailscale set --ssh
exit

If you get a host key warning later: ssh-keygen -R <glkvm-ip>

5. Build Caddy and deploy

# Build Caddy with Cloudflare DNS plugin (cross-compile for GLKVM armv7)
GOOS=linux GOARCH=arm GOARM=7 ~/go/bin/xcaddy build --with github.com/caddy-dns/cloudflare

# Deploy everything
./deploy.sh --full

This will: generate the Caddyfile → create DNS records → upload all files → restart services → verify.

Adding a new server

  1. Edit servers.json — add an entry:

    "my-server": {
      "name": "My Server",
      "host": "100.x.x.x",
      "port": 22,
      "mac": "aa:bb:cc:dd:ee:ff",
      "redirect": null
    }
    • host / port: Tailscale IP + port to check if the server is up
    • mac: MAC address for Wake-on-LAN
    • redirect: URL to redirect to when online (or null for wake-only)
    • alias (optional): custom subdomain, e.g. "alias": "watch"watch.example.com
  2. Run ./deploy.sh

That's it. The deploy script auto-generates the Caddyfile, creates DNS records, uploads configs, and restarts services.

Each server gets on.{name}.{domain} automatically. With an alias, it also gets {alias}.{domain}.

Day-to-day usage

# Deploy after any config change
./deploy.sh

# Deploy after GLKVM firmware update (rebuilds Caddy binary too)
./deploy.sh --full

# Just sync DNS records
python3 sync-dns.py

# Just verify all domains
python3 verify.py

# Wake a server from the GLKVM CLI
ssh root@glkvm wakeonlan media-server

Project structure

File Purpose
servers.json Server definitions — the only file you edit to add servers (gitignored)
servers.example.json Template with dummy values
.env Cloudflare API token + SSH target (gitignored)
deploy.sh One-command deploy: generate → DNS → upload → restart → verify
generate-caddyfile.py Auto-generates Caddyfile from servers.json
sync-dns.py Creates/updates Cloudflare A records from servers.json
verify.py Checks all domains are reachable with valid TLS
wol-gateway.py Multi-domain WoL gateway (reads servers.json at runtime)
wakeonlan WoL CLI with hostname aliases (reads from servers.json)
Caddyfile Auto-generated — don't edit manually (gitignored)
nginx-kvmd.conf Modified KVM nginx config (gitignored)
S98wol-gateway Init script for WoL gateway
S99caddy Init script for Caddy

Deployed to the GLKVM

Local Remote
wol-gateway.py, servers.json /opt/jellyfin-gateway/
wakeonlan /usr/bin/wakeonlan
Caddyfile, .env /etc/caddy/
nginx-kvmd.conf /etc/kvmd/nginx-kvmd.conf
S98wol-gateway, S99caddy /etc/init.d/
caddy (built binary) /usr/bin/caddy

Secrets

  • Cloudflare API token: local .env (gitignored), deployed to /etc/caddy/.env (mode 600)
  • GLKVM_SSH: SSH target for deploy (e.g. root@glkvm), in .env (gitignored). Defaults to root@glkvm if unset
  • S99caddy sources /etc/caddy/.env at startup — no token in the init script
  • servers.json contains Tailscale IPs and MAC addresses (gitignored)

Troubleshooting

# Verify all domains
python3 verify.py

# Check port 443 split
ssh root@glkvm 'ss -tlnp | grep :443'
# Expected: caddy on Tailscale IP, nginx on LAN IP + 127.0.0.1

# Check services
ssh root@glkvm 'pgrep -a caddy; pgrep -a python3; pgrep -a nginx'

# WoL gateway logs
ssh root@glkvm 'cat /var/log/wol-gateway.log'

# Restart everything
ssh root@glkvm '/etc/init.d/S98wol-gateway restart && /etc/init.d/S99caddy restart && /etc/init.d/S99kvmd-nginx stop && /etc/init.d/S99kvmd-nginx start'

Requirements

  • GL.iNet GLKVM (or any always-on Linux box with Tailscale on your LAN)
  • Tailscale on all clients and servers
  • Cloudflare DNS for your domain (DNS-only, grey cloud)
  • Go + xcaddy on your Mac (for building Caddy with Cloudflare plugin)

About

Wake-on-LAN gateway for GL.iNet GLKVM — wake home servers via URL with valid TLS

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors