Setting up a public URL that flashes my office lights

Published: (January 5, 2026 at 01:38 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

The Problem

My Pi is behind my home router. I don’t want to port‑forward or expose Home Assistant directly, but I want to trigger it from the internet. The droplet solves this—it’s public‑facing. The question is: how do I get from the droplet to my Pi securely?

The Solution: Tailscale

Tailscale creates a mesh VPN between devices. Install it on both the droplet and the Pi, and they can talk to each other using private IPs (like 100.x.x.x)—no port forwarding needed.

Internet → Droplet (public) → Tailscale → Pi (private) → Home Assistant

What Claude Code Built

I used Claude Code to wire this up. My key insight was that I could simply give Claude Code SSH access to both my Pi and droplet and let it handle a lot of the rest.

It:

  • SSHed into my Pi and queried Home Assistant to find my light entity IDs
  • Wrote a bash script that flashes the lights red, then restores the previous color
  • Installed Tailscale on both the Pi and droplet
  • Generated SSH keys so the droplet can run commands on the Pi
  • Created a Flask webhook with token‑based auth
  • Set up nginx to route requests
  • Created systemd services so everything survives reboots

The whole thing took maybe 20 minutes. Most of that was waiting for apt to install packages.

The Architecture

Request: GET /flash-peter-office-lights?auth_token=xxx

Cloudflare (HTTPS)

DigitalOcean Droplet
nginx → Flask (port 5000)

Tailscale (100.x.x.x)

Raspberry Pi
SSH → flash_lights.sh

Home Assistant API

Lights flash red → restore

The Flash Script

The tricky part is restoring the lights to their previous state. Home Assistant lights can be in different color modes, so the script saves the current state before flashing:

# Save current state
STATE=$(curl -s -H "Authorization: Bearer $HA_TOKEN" \
  "http://localhost:8123/api/states/light.office")
WAS_ON=$(echo $STATE | jq -r '.state')
BRIGHTNESS=$(echo $STATE | jq -r '.attributes.brightness // 255')
XY_X=$(echo $STATE | jq -r '.attributes.xy_color[0] // empty')
XY_Y=$(echo $STATE | jq -r '.attributes.xy_color[1] // empty')

# Flash red
curl -s -X POST "http://localhost:8123/api/services/light/turn_on" \
  -H "Authorization: Bearer $HA_TOKEN" \
  -d '{"entity_id": "light.office", "rgb_color": [255, 0, 0], "brightness": 255}'

sleep 1

# Restore
curl -s -X POST "http://localhost:8123/api/services/light/turn_on" \
  -H "Authorization: Bearer $HA_TOKEN" \
  -d "{\"entity_id\": \"light.office\", \"brightness\": $BRIGHTNESS, \"xy_color\": [$XY_X, $XY_Y]}"

The first version only saved brightness. When I told Claude Code “the lights aren’t going back to where they were,” it added the xy_color handling.

The Webhook (Flask)

from flask import Flask, request, jsonify
import subprocess
import json

app = Flask(__name__)

def load_tokens():
    with open('/root/webhooks/tokens.json') as f:
        return json.load(f)

@app.route('/flash-peter-office-lights')
def flash():
    token = request.args.get('auth_token')
    if not token:
        return jsonify({"error": "Missing auth_token"}), 401

    tokens = load_tokens()
    if token not in tokens:
        return jsonify({"error": "Invalid token"}), 403

    # SSH to Pi via Tailscale and run the flash script
    cmd = 'ssh -i /root/.ssh/pi_key peter@100.x.x.x "/home/peter/flash_lights.sh"'
    subprocess.run(cmd, shell=True, timeout=15)

    return jsonify({"status": "flashed", "user": tokens[token]["name"]})

Tokens live in a JSON file:

{
  "alice-token-123": {"name": "Alice", "created": "2026-01-05"},
  "bob-token-456": {"name": "Bob", "created": "2026-01-05"}
}

Each person gets their own token. Revoke access by deleting their entry.

What’s Next

Now that the plumbing exists, I can:

  • Different colors for different sources – blue for Slack, green for family texts, red for emergencies
  • Slack slash command/flash-peter for coworkers
  • iOS Shortcut – one‑tap button for my wife
  • Rate limiting – prevent abuse
  • Logging – who flashed and when

If you want to build something similar, the pieces are: a Raspberry Pi (or another device running Home Assistant), a cheap VPS with Tailscale on both ends, and some basic Python/Bash (in my case, written by Claude).

Back to Blog

Related posts

Read more »

The RGB LED Sidequest 💡

markdown !Jennifer Davishttps://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%...

Mendex: Why I Build

Introduction Hello everyone. Today I want to share who I am, what I'm building, and why. Early Career and Burnout I started my career as a developer 17 years a...