Setting up a public URL that flashes my office lights
I've been running Home Assistant on a Raspberry Pi for over two years. It controls my Hue lights, Zigbee devices, the usual stuff. It's always been a local network thing.
As every vibecoder knows, you've got it working on https://localhost:3000 but that doesn't mean you can show your friends type of thing.
I had this little home network but I never wanted to expose it to the broader internet.
This past weekend, I spun up a $4/month DigitalOcean droplet to run ntfy, a self-hosted push notification service. The idea was to give various automations a way to send push notifications to my phone.
Then I realized: now I've got a droplet which can serve as my internet-facing endpoint. That means I can probably gateway to trigger things on my home network via HomeAssistant.
So, I now have a URL that anyone with a token can hit to flash my office lights red - pretty much an IRL ping that something needs my attention.
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.
I described what I wanted, and 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]}"
First version only saved brightness. When I told Claude Code "the lights aren't going back to where they were," it figured out the issue and 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 [email protected] "/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-peterfor 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: Raspberry Pi (or another device to run HomeAssistant), a cheap VPS with Tailscale on both ends, and some basic Python/bash (in my case, written by Claude).